欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

ffmpeg播放器實現詳解之框架搭建過程

 更新時間:2020年07月16日 09:01:47   作者:斷點實驗室  
這篇文章主要介紹了ffmpeg播放器實現詳解之框架搭建過程,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下

ffplay是ffmpeg源碼中一個自帶的開源播放器實例,同時支持本地視頻文件的播放以及在線流媒體播放,功能非常強大。

FFplay: FFplay is a very simple and portable media player using the FFmpeg libraries and the SDL library. It is mostly used as a testbed for the various FFmpeg APIs.

ffplay中的代碼充分調用了ffmpeg中的函數庫,因此,想學習ffmpeg的使用,或基于ffmpeg開發(fā)一個自己的播放器,ffplay都是一個很好的切入點。

由于ffmpeg本身的開發(fā)文檔比較少,且ffplay播放器源碼的實現相對復雜,除了基礎的ffmpeg組件調用外,還包含視頻幀的渲染、音頻幀的播放、音視頻同步策略及線程調度等問題。

因此,這里我們以ffmpeg官網推薦的一個ffplay播放器簡化版本的開發(fā)例程為基礎,在此基礎上循序漸進由淺入深,最終探討實現一個視頻播放器的完整邏輯。

ffplay播放器簡化版本開發(fā)例程可在ffmpeg官網[documentation]頁面的右下角找到,點擊An FFmpeg and SDL Tutorial即可打開找到對應的源碼。

1、項目編譯環(huán)境搭建

這里仍以Ubuntu 16.04 LTS為基礎進行講述,由于ffmpeg支持多個主流平臺,且api接口在各個平臺是一致的,因此其他平臺也可參照本文內容,后續(xù)會將代碼移植到windows等其他平臺,方便大家調試。

源碼的編譯除了ffmpeg環(huán)境外,還需要SDL-1.x版本的支持,用于提供視頻幀的渲染及音頻幀的播放。

1.1 sdl庫編譯

SDL(Simple DirectMedia Layer)是一個跨平臺的多媒體和游戲開發(fā)包,提供2D,音頻,事件驅動,多線程和定時器等服務,它使用C語言寫成,提供了多種控制圖像、聲音、輸出的函數,讓開發(fā)者只要用相同或是相似的代碼就可以開發(fā)出跨多個平臺(Linux、Windows、Mac OS X等)的應用軟件。

SDL: Simple DirectMedia Layer is a cross-platform development library designed to provide low level access to audio, keyboard, mouse, joystick, and graphics hardware via OpenGL and Direct3D. It is used by video playback software, emulators, and popular games including Valve's award winning catalog and many Humble Bundle games.

可通過下面的鏈接下載SDL-1.2.15源碼,注意,例程中依賴的SDL版本與ffplay中有所不同

https://www.libsdl.org/download-1.2.php

下載完成后解壓進入sdl源碼目錄,可通過下面的配置方法生成Makefile文件

./configure --prefix=/usr/local/3rdparty/sdl

生成Makefile文件后,輸入make命令即可開始編譯過程,編譯完成后,執(zhí)行make install命令進行安裝

make 
make install

安裝完成后,會在configure指定的目錄下找到sdl的目錄,由于sdl以庫文件的方式提供支持,因此在sdl/bin目錄下沒有對應的可執(zhí)行文件。

1.2 sdl環(huán)境變量配置

sdl編譯完成后,還需要讓系統(tǒng)能夠找到對應的安裝位置。打開/etc/profile配置文件,在該文件底部添加sdl的環(huán)境變量

#SDL ENVIRONMENT
export C_INCLUDE_PATH=/usr/local/3rdparty/sdl/include/SDL:$C_INCLUDE_PATH
export LD_LIBRARY_PATH=/usr/local/3rdparty/sdl/lib:$LD_LIBRARY_PATH
export PKG_CONFIG_PATH=/usr/local/3rdparty/sdl/lib/pkgconfig:$PKG_CONFIG_PATH

1.3 項目源碼編譯

項目源碼可采用如下Makefile腳本進行編譯

tutorial01: tutorial01.c
	gcc -o tutorial01 -g3 tutorial01.c -I${FFMPEG_INCLUDE} -I${SDL_INCLUDE} \
	-L${FFMPEG_LIB} -lavutil -lavformat -lavcodec -lswscale -lswresample -lz -lm \
	`sdl-config --cflags --libs`

clean:
	rm -rf tutorial01
	rm -rf *.ppm

執(zhí)行make命令開始編譯,編譯完成后,可在源碼目錄生成名為[tutorial01]的可執(zhí)行文件。

1.4 驗證

與ffplay的使用方法類似,執(zhí)行[tutorial01 url]命令,可以看到在源碼目錄生成的后綴名為.ppm的圖像

./tutorial01 rtmp://58.200.131.2:1935/livetv/hunantv

ppm圖像在linux平臺下可直接打開,看到有ppm圖像生成,即可確定項目能夠正常工作,輸入Ctrl+C結束程序運行。

ppm格式的圖像平時不太常用,大家沒有必要做深入研究,這里僅用于對編譯結果的驗證。

PPM: A PPM file is a 24-bit color image formatted using a text format. It stores each pixel with a number from 0 to 65536, which specifies the color of the pixel. PPM files also store the image height and width, whitespace data, and the maximum color value. The portable pixmap format (PPM), the portable graymap format (PGM) and the portable bitmap format (PBM) are image file formats designed to be easily exchanged between platforms.

2 源碼分析

上述例程除了生成幾張圖片外,好像什么也做不了,似乎離一個功能完整的視頻播放器還有很遠的距離。

盡管如此,例程依然包含了ffmpeg視頻開發(fā)用到的幾乎所有關鍵的api與數據結構。后面的內容會在此基礎上不斷的完善,直至實現一個完整的視頻播放器。

2.1 流程

下面給出例程的流程圖,流程非常簡單,所有代碼都運行在主線程中,流程涉及api及數據結構的含義都在例程源碼中有詳細的注釋。

2.2 源碼中涉及的api及組件

由于篇幅的限制,這里先簡要介紹每個組件及api的含義,后續(xù)文章中會深入介紹每個組件及api的使用方法

組件:

  • AVFormatContext 保存文件容器封裝信息及碼流參數的結構體
  • AVCodecContext 解碼器上下文對象,解碼器依賴的相關環(huán)境、狀態(tài)、資源以及參數集的接口指針
  • AVCodec 保存編解碼器信息的結構體,提供編碼與解碼的公共接口
  • AVPacket 負責保存壓縮編碼數據相關信息的結構體,每幀圖像由一到多個packet包組成
  • AVFrame 保存音視頻解碼后的數據,如狀態(tài)信息、編解碼器信息、宏塊類型表,QP表,運動矢量表等數據
  • SwsContext 描述轉換器參數的結構體

api :

  • av_register_all 注冊所有ffmpeg支持的多媒體格式及編解碼器
  • avformat_open_input 打開視頻文件,讀文件頭內容,取得文件容器的封裝信息及碼流參數并存儲在pFormatCtx中
  • avformat_find_stream_info 取得文件中保存的碼流信息,并填充到pFormatCtx->stream 字段
  • avcodec_find_decoder 根據視頻流對應的解碼器上下文查找對應的解碼器,返回對應的解碼器
  • avcodec_alloc_context3 復制編解碼器上下文對象,用于保存從視頻流中抽取的幀
  • avcodec_open2 打開解碼器
  • av_frame_alloc 為解碼后的視頻信息結構體分配空間并完成初始化操作
  • av_read_frame 從文件中依次讀取每個圖像編碼數據包,并存儲在AVPacket數據結構中
  • avcodec_decode_video2 解碼完整的一幀數據,若一個packet無法解碼一個完整的視頻幀,則在ffmpeg后臺維護的緩存隊列會持續(xù)等待多個packet,直到能夠解碼出一個完整的視頻幀為止

3 ffmpeg能幫我們做什么

視頻開發(fā)涉及到多種視頻格式的編解碼,多種文件格式及傳輸協議的解封裝等操作,很難一下子全部掌握。

ffmpeg通過其封裝的api及組件,為我們屏蔽了不同視頻封裝格式及編碼格式的差異,以統(tǒng)一的api接口提供給開發(fā)者使用,開發(fā)者不需要了解每種編碼方式及封裝方式具體的技術細節(jié),只需要調用ffmpeg提供的api就可以完成解封裝和解碼的操作了。

至于視頻幀的渲染及音頻幀的播放,ffmpeg就無能為力了,因此需要借助類似sdl庫等其他組件完成,后面的章節(jié)會為大家介紹繼續(xù)介紹。

4 源碼清單

// tutorial01.c
// Code based on a tutorial by Martin Bohme (boehme@inb.uni-luebeckREMOVETHIS.de)
// Tested on Gentoo, CVS version 5/01/07 compiled with GCC 4.1.1
// With updates from https://github.com/chelyaev/ffmpeg-tutorial
// Updates tested on:
// LAVC 54.59.100, LAVF 54.29.104, LSWS 2.1.101 
// on GCC 4.7.2 in Debian February 2015
//
// Updates tested on:
// Mac OS X 10.11.6
// Apple LLVM version 8.0.0 (clang-800.0.38)
//
// A small sample program that shows how to use libavformat and libavcodec to read video from a file.
//
// Use
//
// $ gcc -o tutorial01 tutorial01.c -lavutil -lavformat -lavcodec -lswscale -lz -lm
//
// to build (assuming libavutil/libavformat/libavcodec/libswscale are correctly installed your system).
//
// Run using
//
// $ tutorial01 myvideofile.mpg
//
// to write the first five frames from "myvideofile.mpg" to disk in PPM format.

// comment by breakpointlab@outlook.com

#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>

#include <stdio.h>

// compatibility with newer API
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55,28,1)
#define av_frame_alloc avcodec_alloc_frame
#define av_frame_free avcodec_free_frame
#endif

//保存PPM文件
void SaveFrame(AVFrame *pFrame, int width, int height, int iFrame) {
	FILE *pFile;//定義文件對象
	char szFilename[32];//定義輸出文件名
	
	// Open file,打開文件
	sprintf(szFilename, "frame%d.ppm", iFrame);//格式化輸出文件名
	pFile = fopen(szFilename, "wb");//打開輸出文件
	if (pFile == NULL) {//檢查輸出文件是否打開成功
		return;
	}
 
	// Write header indicated how wide & tall the image is,向輸出文件中寫入文件頭
	fprintf(pFile, "P6\n%d %d\n255\n", width, height);
 
	// Write pixel data,write the file one line a time,一次一行循環(huán)寫入RGB24像素值
	int y;
	for (y = 0; y < height; y++) {
		fwrite(pFrame->data[0]+y*pFrame->linesize[0], 1, width*3, pFile);
	}
 
	// Close file,關閉文件
	fclose(pFile);
}

int main(int argc, char *argv[]) {
/*--------------參數定義-------------*/
	// Initalizing these to NULL prevents segfaults!
	AVFormatContext *pFormatCtx = NULL;//保存文件容器封裝信息及碼流參數的結構體
	AVCodecContext *pCodecCtxOrig = NULL;//解碼器上下文對象,解碼器依賴的相關環(huán)境、狀態(tài)、資源以及參數集的接口指針
	AVCodecContext *pCodecCtx = NULL;//編碼器上下文對象,用于PPM文件輸出
	AVCodec *pCodec = NULL;//保存編解碼器信息的結構體,提供編碼與解碼的公共接口,可以看作是編碼器與解碼器的一個全局變量
	AVPacket packet;//負責保存壓縮編碼數據相關信息的結構體,每幀圖像由一到多個packet包組成
	AVFrame *pFrame = NULL;//保存音視頻解碼后的數據,如狀態(tài)信息、編解碼器信息、宏塊類型表,QP表,運動矢量表等數據
	AVFrame *pFrameRGB = NULL;//保存輸出24-bit RGB的PPM文件數據
	struct SwsContext *sws_ctx = NULL;//描述轉換器參數的結構體

	int numBytes;//RGB24格式數據長度
	uint8_t *buffer = NULL;//解碼數據輸出緩存指針
	int i,videoStream;//循環(huán)變量,視頻流類型標號
	int frameFinished;//解碼操作是否成功標識

/*-------------參數初始化------------*/
	if (argc<2) {//檢查輸入參數個數是否正確
		printf("Please provide a movie file\n");
		return -1;
	}

	// Register all available formats and codecs,注冊所有ffmpeg支持的多媒體格式及編解碼器
	av_register_all();

	/*-----------------------
	 * Open video file,打開視頻文件,讀文件頭內容,取得文件容器的封裝信息及碼流參數并存儲在pFormatCtx中
	 * read the file header and stores information about the file format in the AVFormatContext structure 
	 * The last three arguments are used to specify the file format, buffer size, and format options
	 * but by setting this to NULL or 0, libavformat will auto-detect these
	 -----------------------*/
	if (avformat_open_input(&pFormatCtx, argv[1], NULL, NULL) != 0) {
		return -1; // Couldn't open file.
	}

	/*-----------------------
	 * 取得文件中保存的碼流信息,并填充到pFormatCtx->stream 字段
	 * check out & Retrieve the stream information in the file
	 * then populate pFormatCtx->stream with the proper information 
	 * pFormatCtx->streams is just an array of pointers, of size pFormatCtx->nb_streams
	 -----------------------*/
	if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {
		return -1; // Couldn't find stream information.
	}

	// Dump information about file onto standard error,打印pFormatCtx中的碼流信息
	av_dump_format(pFormatCtx, 0, argv[1], 0);

	// Find the first video stream.
	videoStream=-1;//視頻流類型標號初始化為-1
	for (i=0;i<pFormatCtx->nb_streams;i++) {//遍歷文件中包含的所有流媒體類型(視頻流、音頻流、字幕流等)
		if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO) {//若文件中包含有視頻流
			videoStream = i;//用視頻流類型的標號修改標識,使之不為-1
			break;//退出循環(huán)
		}
	}
	if (videoStream==-1) {//檢查文件中是否存在視頻流
		return -1; // Didn't find a video stream.
	}

	// Get a pointer to the codec context for the video stream,根據流類型標號從pFormatCtx->streams中取得視頻流對應的解碼器上下文
	pCodecCtxOrig = pFormatCtx->streams[videoStream]->codec;
	/*-----------------------
	 * Find the decoder for the video stream,根據視頻流對應的解碼器上下文查找對應的解碼器,返回對應的解碼器(信息結構體)
	 * The stream's information about the codec is in what we call the "codec context.
	 * This contains all the information about the codec that the stream is using
	 -----------------------*/
	pCodec = avcodec_find_decoder(pCodecCtxOrig->codec_id);
	if (pCodec == NULL) {//檢查解碼器是否匹配
		fprintf(stderr, "Unsupported codec!\n");
		return -1; // Codec not found.
	}

	// Copy context,復制編解碼器上下文對象,用于保存從視頻流中抽取的幀
	pCodecCtx = avcodec_alloc_context3(pCodec);//創(chuàng)建AVCodecContext結構體對象pCodecCtx
	if (avcodec_copy_context(pCodecCtx, pCodecCtxOrig) != 0) {//復制編解碼器上下文對象
		fprintf(stderr, "Couldn't copy codec context");
		return -1; // Error copying codec context.
	}

	// Open codec,打開解碼器
	if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
		return -1; // Could not open codec.
	}

	// Allocate video frame,為解碼后的視頻信息結構體分配空間并完成初始化操作(結構體中的圖像緩存按照下面兩步手動安裝)
	pFrame = av_frame_alloc();

	// Allocate an AVFrame structure,為轉換PPM文件的結構體分配空間并完成初始化操作
	pFrameRGB = av_frame_alloc();
	if (pFrameRGB == NULL) {//檢查初始化操作是否成功
		return -1;
	}

	// Determine required buffer size and allocate buffer,根據像素格式及圖像尺寸計算內存大小
	numBytes = av_image_get_buffer_size(AV_PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height, 1);
	buffer = (uint8_t *) av_malloc(numBytes * sizeof(uint8_t));//為轉換后的RGB24圖像配置緩存空間

	// Assign appropriate parts of buffer to image planes in pFrameRGB Note that pFrameRGB is an AVFrame, but AVFrame is a superset of AVPicture
	// 為AVFrame對象安裝圖像緩存,將out_buffer緩存掛到pFrameYUV->data指針結構上
	av_image_fill_arrays(pFrameRGB->data, pFrameRGB->linesize, buffer, AV_PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height, 1);
 
	// Initialize SWS context for software scaling,設置圖像轉換像素格式為AV_PIX_FMT_RGB24
	sws_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_RGB24, SWS_BILINEAR, NULL, NULL, NULL);

/*--------------循環(huán)解碼-------------*/
	i = 0;// Read frames(2 packet) and save first five frames to disk,
	/*-----------------------
	 * read in a packet and store it in the AVPacket struct
	 * ffmpeg allocates the internal data for us,which is pointed to by packet.data
	 * this is freed by the av_free_packet()
	 -----------------------*/
	while (av_read_frame(pFormatCtx, &packet) >= 0) {//從視頻文件或網絡流媒體中依次讀取每個圖像編碼數據包,并存儲在AVPacket數據結構中
		// Is this a packet from the video stream,檢查數據包類型
		if (packet.stream_index == videoStream) {
		 /*-----------------------
	 		 * Decode video frame,解碼完整的一幀數據,并將frameFinished設置為true
			 * 可能無法通過只解碼一個packet就獲得一個完整的視頻幀frame,可能需要讀取多個packet才行
	 		 * avcodec_decode_video2()會在解碼到完整的一幀時設置frameFinished為真
			 * Technically a packet can contain partial frames or other bits of data
			 * ffmpeg's parser ensures that the packets we get contain either complete or multiple frames
			 * convert the packet to a frame for us and set frameFinisned for us when we have the next frame
	 	 	 -----------------------*/
			avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);

			// Did we get a video frame,檢查是否解碼出完整一幀圖像
			if (frameFinished) {
				// Convert the image from its native format to RGB,//將解碼后的圖像轉換為RGB24格式
				sws_scale(sws_ctx, (uint8_t const * const *) pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data, pFrameRGB->linesize);

				if (++i <= 5) {// Save the frame to disk,將前5幀圖像存儲到磁盤上
					SaveFrame(pFrameRGB, pCodecCtx->width, pCodecCtx->height, i);
				}
			}
		}
		// Free the packet that was allocated by av_read_frame,釋放AVPacket數據結構中編碼數據指針
		av_packet_unref(&packet);
	}

/*--------------參數撤銷-------------*/
	// Free the RGB image buffer
	av_free(buffer);
	av_frame_free(&pFrameRGB);

	// Free the YUV frame.
	av_frame_free(&pFrame);

	// Close the codecs.
	avcodec_close(pCodecCtx);
	avcodec_close(pCodecCtxOrig);

	// Close the video file.
	avformat_close_input(&pFormatCtx);

	return 0;
}

到此這篇關于ffmpeg播放器實現詳解之 框架搭建過程的文章就介紹到這了,更多相關ffmpeg播放器框架搭建內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • uniApp微信小程序使用騰訊地圖定位功能及getLocation需要在app.json中聲明permission字段問題解決

    uniApp微信小程序使用騰訊地圖定位功能及getLocation需要在app.json中聲明permission字段問

    這篇文章主要介紹了uniApp微信小程序使用騰訊地圖定位功能及getLocation需要在app.json中聲明permission字段問題解決,需要的朋友可以參考下
    2022-12-12
  • git merge --ff/--no-ff/--ff-only 三種選項參數的區(qū)別解析

    git merge --ff/--no-ff/--ff-only 三種選項參數的區(qū)別解析

    這篇文章主要介紹了git merge --ff/--no-ff/--ff-only 三種選項參數的區(qū)別解析,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-04-04
  • Scala函數式編程專題--scala基礎語法介紹

    Scala函數式編程專題--scala基礎語法介紹

    這篇文章主要介紹了scala基礎語法的的相關資料,文中講解非常詳細,幫助大家更好的理解和學習,感興趣的朋友可以了解下
    2020-06-06
  • K8S二進制部署的K8S(1.15)部署hpa功能

    K8S二進制部署的K8S(1.15)部署hpa功能

    這篇文章主要介紹了K8S二進制部署的K8S(1.15)部署hpa功能,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-04-04
  • 淺談遷移學習

    淺談遷移學習

    遷移學習(Transfer Learning)是一種機器學習方法,是把一個領域(即源領域)的知識,遷移到另外一個領域(即目標領域),使得目標領域能夠取得更好的學習效果
    2021-06-06
  • TCP的三次握手與四次揮手詳細介紹

    TCP的三次握手與四次揮手詳細介紹

    這篇文章主要介紹了TCP的三次握手與四次揮手詳細介紹的相關資料,需要的朋友可以參考下
    2017-03-03
  • Sqoop的安裝與使用詳細教程

    Sqoop的安裝與使用詳細教程

    Sqoop是一款開源的工具,主要用于在Hadoop與傳統(tǒng)數據庫間進行數據的傳遞,這篇文章主要介紹了Sqoop的安裝與使用詳細教程,需要的朋友可以參考下
    2021-04-04
  • 漫談架構之微服務

    漫談架構之微服務

    微服務的架構出現已經很久很久了,微服務架構就是一種將單個應用程序轉換為一組小服務的方法,每個小服務都在自己的進程中運行,并使用輕量級的交互方式(如HTTP)進行通信
    2021-06-06
  • 微信支付jsapi缺少參數 total_fee 錯誤分析與解決方法

    微信支付jsapi缺少參數 total_fee 錯誤分析與解決方法

    這篇文章主要介紹了微信支付jsapi缺少參數 total_fee 錯誤分析與解決方法,需要的朋友可以參考下
    2018-03-03
  • 趣味函數式編程圣經

    趣味函數式編程圣經

    這篇文章主要介紹了函數式編程的的相關資料,有趣的講解了函數式編程的相關知識,幫助大家更好的理解學習,感興趣的朋友可以了解下
    2020-06-06

最新評論