標籤:ffmpeg 記憶體 播放 解碼
打算記錄兩個最簡單的FFmpeg進行記憶體讀寫的例子。之前的所有有關FFmpeg的例子都是對檔案進行操作的。例如《100行代碼實現最簡單的基於FFMPEG+SDL的視頻播放器》播放的是一個視頻的檔案。而《最簡單的基於FFMPEG的轉碼程式》也是將一個視頻檔案轉換為另一個視頻檔案。《最簡單的基於FFmpeg的視頻編碼器(YUV編碼為H.264)》也是最後編碼得到一個H.264視頻檔案。實際上,並不是所有視頻的編碼,解碼都是針對檔案進行處理的。有的時候需要的解碼的視頻資料在一段記憶體中。例如,通過其他系統送來的視頻資料。同樣,有的時候編碼後的視頻資料也未必要儲存成一個檔案。例如,要求將編碼後的視頻資料送給其他的系統進行下一步的處理。以上兩種情況就要求FFmpeg不僅僅是對檔案進行“讀,寫”操作,而是要對記憶體進行“讀,寫”操作。因此打算記錄的兩個例子就是使用FFmpeg對記憶體進行讀寫的例子。
有關FFmpeg讀寫記憶體的例子已經在文章《ffmpeg 從記憶體中讀取資料(或將資料輸出到記憶體)》中有過敘述,但是一直沒有做完整代碼的工程。本文記錄《最簡單的基於FFmpeg記憶體播放器》。該例子中,首先將檔案中的視頻資料通過fread()讀取到記憶體中,然後使用FFmpeg播放記憶體中的資料。
下篇文章計劃記錄的第二個例子是《最簡單的基於FFmpeg記憶體轉碼器》。該例子中,首先將檔案中的視頻資料通過fread()讀取到記憶體中,然後使用FFmpeg讀取該資料並進行轉碼,接著將轉碼後的資料輸出到另一塊記憶體中,最後將該資料通過fwrite()寫入成檔案。
關於如何從記憶體中讀取資料在這裡不再詳述,可以參考文章:
ffmpeg 從記憶體中讀取資料(或將資料輸出到記憶體)
關鍵點
關鍵點就兩個:
1. 初始化自訂的AVIOContext,指定自訂的回呼函數。範例程式碼如下:
//AVIOContext中的緩衝unsigned char *aviobuffer=(unsigned char*)av_malloc(32768);AVIOContext *avio=avio_alloc_context(aviobuffer, 32768,0,NULL,read_buffer,NULL,NULL);pFormatCtx->pb=avio; if(avformat_open_input(&pFormatCtx,NULL,NULL,NULL)!=0){ printf("Couldn‘t open inputstream.(無法開啟輸入資料流)\n"); return -1;}
上述代碼中,自訂了回呼函數read_buffer()。在使用avformat_open_input()開啟媒體資料的時候,就可以不指定檔案的URL了,即其第2個參數為NULL(因為資料不是靠檔案讀取,而是由read_buffer()提供)
2. 自己寫回呼函數。範例程式碼如下:
//Callbackint read_buffer(void *opaque, uint8_t *buf, int buf_size){if(!feof(fp_open)){inttrue_size=fread(buf,1,buf_size,fp_open);return true_size;}else{return -1;}}
當系統需要資料的時候,會自動調用該回呼函數以擷取資料。這個例子為了簡單,直接使用fread()讀取資料至記憶體。回呼函數需要格外注意它的參數和傳回值。
原始碼
下面直接貼上程式的原始碼:
/** * 最簡單的基於FFmpeg的記憶體讀寫例子(記憶體播放器) * Simplest FFmpeg mem Player * * 雷霄驊 * [email protected] * 中國傳媒大學/數字電視技術 * Communication University of China / Digital TV Technology * http://blog.csdn.net/leixiaohua1020 * * 本程式實現了對記憶體中的視頻資料的播放。 * 是最簡單的使用FFmpeg讀記憶體的例子。 * * This software play video data in memory (not a file). * It‘s the simplest example to use FFmpeg to read from memory. * */#include <stdio.h>extern "C"{#include "libavcodec/avcodec.h"#include "libavformat/avformat.h"#include "libswscale/swscale.h"//SDL#include "sdl/SDL.h"#include "sdl/SDL_thread.h"};//Output YUV420P #define OUTPUT_YUV420P 0FILE *fp_open=NULL;//Callbackint read_buffer(void *opaque, uint8_t *buf, int buf_size){if(!feof(fp_open)){int true_size=fread(buf,1,buf_size,fp_open);return true_size;}else{return -1;}}int main(int argc, char* argv[]){AVFormatContext*pFormatCtx;inti, videoindex;AVCodecContext*pCodecCtx;AVCodec*pCodec;char filepath[]="cuc60anniversary_start.mkv";av_register_all();avformat_network_init();pFormatCtx = avformat_alloc_context();fp_open=fopen(filepath,"rb+");//AVIOContext中的緩衝unsigned char *aviobuffer=(unsigned char *)av_malloc(32768);AVIOContext *avio =avio_alloc_context(aviobuffer, 32768,0,NULL,read_buffer,NULL,NULL);pFormatCtx->pb=avio;if(avformat_open_input(&pFormatCtx,NULL,NULL,NULL)!=0){printf("Couldn‘t open input stream.(無法開啟輸入資料流)\n");return -1;}if(av_find_stream_info(pFormatCtx)<0){printf("Couldn‘t find stream information.(無法擷取流資訊)\n");return -1;}videoindex=-1;for(i=0; i<pFormatCtx->nb_streams; i++) if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO){videoindex=i;break;}if(videoindex==-1){printf("Didn‘t find a video stream.(沒有找到視頻流)\n");return -1;}pCodecCtx=pFormatCtx->streams[videoindex]->codec;pCodec=avcodec_find_decoder(pCodecCtx->codec_id);if(pCodec==NULL){printf("Codec not found.(沒有找到解碼器)\n");return -1;}if(avcodec_open2(pCodecCtx, pCodec,NULL)<0){printf("Could not open codec.(無法開啟解碼器)\n");return -1;}AVFrame*pFrame,*pFrameYUV;pFrame=avcodec_alloc_frame();pFrameYUV=avcodec_alloc_frame();uint8_t *out_buffer=(uint8_t *)av_malloc(avpicture_get_size(PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height));avpicture_fill((AVPicture *)pFrameYUV, out_buffer, PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);//SDL----------------------------if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) { printf( "Could not initialize SDL - %s\n", SDL_GetError()); return -1;} int screen_w=0,screen_h=0;SDL_Surface *screen; screen_w = pCodecCtx->width;screen_h = pCodecCtx->height;screen = SDL_SetVideoMode(screen_w, screen_h, 0,0);if(!screen) { printf("SDL: could not set video mode - exiting:%s\n",SDL_GetError()); return -1;}SDL_Overlay *bmp; bmp = SDL_CreateYUVOverlay(pCodecCtx->width, pCodecCtx->height,SDL_YV12_OVERLAY, screen); SDL_Rect rect;//SDL End------------------------int ret, got_picture;AVPacket *packet=(AVPacket *)av_malloc(sizeof(AVPacket));//Output Information-----------------------------printf("File Information---------------------------------\n");av_dump_format(pFormatCtx,0,filepath,0);printf("-------------------------------------------------\n");#if OUTPUT_YUV420P FILE *fp_yuv=fopen("output.yuv","wb+"); #endif struct SwsContext *img_convert_ctx;img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL); //------------------------------while(av_read_frame(pFormatCtx, packet)>=0){if(packet->stream_index==videoindex){ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);if(ret < 0){printf("Decode Error.(解碼錯誤)\n");return -1;}if(got_picture){sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);#if OUTPUT_YUV420Pint y_size=pCodecCtx->width*pCodecCtx->height; fwrite(pFrameYUV->data[0],1,y_size,fp_yuv); //Y fwrite(pFrameYUV->data[1],1,y_size/4,fp_yuv); //Ufwrite(pFrameYUV->data[2],1,y_size/4,fp_yuv); //V#endifSDL_LockYUVOverlay(bmp);bmp->pixels[0]=pFrameYUV->data[0];bmp->pixels[2]=pFrameYUV->data[1];bmp->pixels[1]=pFrameYUV->data[2]; bmp->pitches[0]=pFrameYUV->linesize[0];bmp->pitches[2]=pFrameYUV->linesize[1]; bmp->pitches[1]=pFrameYUV->linesize[2];SDL_UnlockYUVOverlay(bmp); rect.x = 0; rect.y = 0; rect.w = screen_w; rect.h = screen_h; SDL_DisplayYUVOverlay(bmp, &rect); //Delay 40msSDL_Delay(40);}}av_free_packet(packet);}sws_freeContext(img_convert_ctx);#if OUTPUT_YUV420P fclose(fp_yuv);#endif fclose(fp_open);SDL_Quit();av_free(out_buffer);av_free(pFrameYUV);avcodec_close(pCodecCtx);avformat_close_input(&pFormatCtx);return 0;}
可以通過代碼定義的宏來確定是否將解碼後的YUV420P資料輸出成檔案:
#define OUTPUT_YUV420P 0
結果
程式的運行結果如下。可以解碼播放測試視頻。適逢60周年校慶,因此截取了一小段校慶晚會的開場畫面作為測試視頻,給母校慶生~
下載
SourceForge項目首頁:
https://sourceforge.net/projects/simplestffmpegmemhandler/
CSDN項目:
http://download.csdn.net/detail/leixiaohua1020/8003731
註:
本工程包含兩個FFmpeg讀寫記憶體的例子:
simplest_ffmpeg_mem_player:基於FFmpeg的記憶體播放器。
simplest_ffmpeg_mem_transcoder:基於FFmpeg的記憶體轉碼器(下篇文章記錄)。
最簡單的基於FFmpeg的記憶體讀寫的例子:記憶體播放器