根據ffmpeg官方網站上的例子程序開始學習ffmpeg和SDL編程。
SDL是一個跨平臺的多媒體開發包。適用于游戲,模擬器,播放器等應用軟件開發。支持linux 、win32 等操作系統。
主要應用:
視頻
|
事件
- 提供以下事件:
-
- 應用程序的visibility發生改變
- 鍵盤輸入
- 鼠標輸入
- 用戶要求的退出
- 每種事件都能通過SDL_EventState()關閉或者打開。
- 事件經由用戶指定的過濾函數再被加入到內部的事件隊列。
- 線程安全的事件隊列。
音頻
- 設置8位和16位的音頻,單聲道或者立體聲,如果格式硬件不支持,可以選擇轉換。
- 由獨立的線程執行音頻部分,并提供用戶回調(callback)機制。
- 設計上考慮到了客戶定制的軟混音器,但實際上在例程中就包含了一個完整的音頻/音樂輸出庫。
CD音頻
- 完整的CD音頻控制API
線程
- 簡單的線程創建API
- 用于同步的簡單的二進制信號量(semaphores)?
定時器
- 讀取已流逝的毫秒數。
- 等待指定的毫秒數。
- 設置一個10毫秒精度的周期性定時器。
字節序無關
- 偵測當前系統的字節序
- 快速轉換數據的函數
- 讀寫指定字節序的數據
這里我們使用SDL作為音視頻輸出對象,ffmpeg完成音視頻的解碼。
像使用其他軟件包或者開發庫一樣,首先肯定要初始化相應的庫,然后才能夠使用,初始化函數如下:
if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER))
??? ??? {
????????? fprintf(stderr, "Could not initialize SDL - %s/n", SDL_GetError());
????????? return -1 ;
??? ??? }
SDL有很多方法是實現視頻的輸出,但是YUV overlay是一種簡單而又常用的方法,具體使用方法是:
首先創建一個surface用來顯示視頻數據,然后創建一個overlay,然后就可以通過overlay輸出視頻到surface
其創建過程如下:
int? init_sdl(int width ,int height)
{
??
??? //create screen for displaying
??? screen = SDL_SetVideoMode(width, height, 0, 0);
??? if(!screen)
??? {
????????? fprintf(stderr, "SDL: could not set video mode - exiting/n");
????????? return -1 ;
??? }
??
??? //Now we create a YUV overlay on that screen so we can input video to it:
??? bmp = SDL_CreateYUVOverlay(width, height,
?????????????????????????? SDL_YV12_OVERLAY, screen);
??? return 0 ;
}
創建后就可以顯示視頻數據了,我對此進行了簡單的封裝,如下:
//顯示函數,提取一個完整的視頻幀后,就可以顯示此函數
void sdl_display(AVPicture *pict,SDL_Overlay *bmp,enum PixelFormat src_fmt,int width,int height)
{
??? SDL_Rect rect ;
?? struct SwsContext *img_convert_ctx=NULL;
?? AVPicture p;
?? SDL_LockYUVOverlay(bmp);
?????????????
???? p.data[0] = bmp->pixels[0];
???? p.data[1] = bmp->pixels[2];
???? p.data[2] = bmp->pixels[1];
????? p.linesize[0] = bmp->pitches[0];
????? p.linesize[1] = bmp->pitches[2];
????? p.linesize[2] = bmp->pitches[1];
?
???? //視頻格式轉化為YUV420P格式
?????? img_convert_ctx=sws_getCachedContext(img_convert_ctx,width,height,
???????????????????? src_fmt,width,height,PIX_FMT_YUV420P,
??????????????????? SWS_X ,NULL,NULL,NULL) ;
?????? if (img_convert_ctx == NULL)
?????? {
???????????????????????
???????????????? printf("can't init convert context!/n") ;
???????????????? return ;
??????? }
????? sws_scale(img_convert_ctx, pict->data, pict->linesize,
?????????????????????????? 0,width, p.data, p.linesize);
?????
?????????????
?????? SDL_UnlockYUVOverlay(bmp);
?//設置顯示區域的位置和大小
?????? rect.x = 0;
?????? rect.y = 0;
?????? rect.w = width;
?????? rect.h = height;
??? //顯示視頻幀
?????? SDL_DisplayYUVOverlay(bmp, &rect);
?
}
這樣在解碼出一幀數據后就可以通過調用此函數完成視頻的顯示了
視頻顯示搞定了,那么該輪到音頻輸出
要想輸出音頻,首先必須得打開音頻設備,SDL對音頻設備的打開和初始化已經做好了封裝,我們通過調用SDL_OpenAudio 來打開和初始化音頻設備,通過結構體 SDL_AudioSpec 設置相應的參數,然后將參數通過 SDL_OpenAudio 設置好設備,封裝如下:
SDL_AudioSpec audio_spec ,spec;
int init_sdl_audio(AVCodecContext *aCodecCtx)
{
??? audio_spec.freq = aCodecCtx->sample_rate;
??? audio_spec.format = AUDIO_S16SYS;
??? audio_spec.channels = aCodecCtx->channels;
??? audio_spec.silence = 0;
??? audio_spec.samples = SDL_AUDIO_BUFFER_SIZE;
??? audio_spec.callback = audio_callback;
??? audio_spec.userdata = aCodecCtx;
??? if(SDL_OpenAudio(&audio_spec, &spec) < 0)
??? {
? ??? ??? fprintf(stderr, "SDL_OpenAudio: %s/n", SDL_GetError());
? ??? ??? return -1;
??? }
??? return 0 ;
}
其他就和視頻一樣了,先分解出音頻流,然后根據音頻流找出解碼上下文,再根據解碼上下文找到解碼器,并打開了,接著就可以進行解碼了。
但是我們不能想解碼視頻一樣,直接對音頻包進行解碼,我們不斷從文件中的packet,同時SDL又要不斷的調用回調函數,解決的辦法是創建一個互斥隊列,ffmpeg已經為我們封裝了一個AVPacketList結構體,我們需要對此進行再次封裝如下:
typedef struct PacketQueue {
?
? AVPacketList
?
?*first_pkt, *last_pkt;
?
? int nb_packets;
?
? int size;
?
? SDL_mutex
?
?*mutex;
?
? SDL_cond
?
?*cond;
?
} PacketQueue;
?
我們得注意:這里的size是packet的大小,而nb_packets是隊列中packet的個數。
?
對于一個隊列首先得有一個初始化函數,完成初始化
?
void packet_queue_init(PacketQueue *q) {
?
? memset(q, 0, sizeof(PacketQueue));
?
? q->mutex = SDL_CreateMutex
?
();
?
? q->cond = SDL_CreateCond
?
();
?
}
?
很明顯這個初始化函數完成了隊列的內存分配、互斥量和條件量的創建。
?
然后就是入隊和出隊的函數
?
//put audio packet in the queue
?
int packet_queue_put(PacketQueue *q, AVPacket *pkt) {
?
?
? AVPacketList *pkt1;
?
//if pkt is not allocated ,allocate it
?
? if(av_dup_packet(pkt) < 0) {
?
??? return -1;
?
? }
?
//allocate space for new member of queue
pkt1 = av_malloc(sizeof(AVPacketList));
?
? if (!pkt1)
?
??? return -1;
?
//put pkt in pkt1
?
? pkt1->pkt = *pkt;
?
? pkt1->next = NULL;
?
?
?
//lock queue and wait until finishing put
?
? SDL_LockMutex(q->mutex);
?
//if last_pkt is NULL,it means that the queue is NULL,so put the packet in the first position
?
? if (!q->last_pkt)
?
??? q->first_pkt = pkt1;
?
? else
?
??? q->last_pkt->next = pkt1;
?
? q->last_pkt = pkt1;
?
? q->nb_packets++;
?
? q->size += pkt1->pkt.size;
?
//send signal of finish
?
? SDL_CondSignal(q->cond);
?
?
?
? SDL_UnlockMutex(q->mutex);
?
? return 0;
?
}
?
?
//put audio packet in the queue
?
static int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block) {
?
? AVPacketList *pkt1;
?
? int ret;
?
?
?
? SDL_LockMutex(q->mutex);
?
?
?
? for(;;) {
?
???
?
??? if(quit) {
?
????? ret = -1;
?
????? break;
?
??? }
?
?
??? pkt1 = q->first_pkt;
?
??? if (pkt1) {
?
????? q->first_pkt = pkt1->next;
?
????? if (!q->first_pkt)
?
??????? q->last_pkt = NULL;
?
????? q->nb_packets--;
?
????? q->size -= pkt1->pkt.size;
?
????? *pkt = pkt1->pkt;
?
????? av_free(pkt1);
?
????? ret = 1;
?
????? break;
?
??? } else if (!block) {
?
????? ret = 0;
?
????? break;
?
??? } else {
?
????? SDL_CondWait(q->cond, q->mutex);
?
??? }
?
? }
?
? SDL_UnlockMutex(q->mutex);
?
? return ret;
?
}
?
這里我們必須得注意SDL為音頻處理創建了一個單獨的線程,線程中通過調用回調函數完成從包中解碼出音頻幀
?
然后再調用解碼函數將音頻幀解碼出來!
?
void audio_callback(void *userdata, Uint8 *stream, int len) {
?
?
? AVCodecContext *aCodecCtx = (AVCodecContext *)userdata;
?
? int len1, audio_size;
?
?
? static uint8_t audio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2];
?
? static unsigned int audio_buf_size = 0;
?
? static unsigned int audio_buf_index = 0;
?
?
? while(len > 0) {
?
??? if(audio_buf_index >= audio_buf_size) {
audio_size = audio_decode_frame(aCodecCtx, audio_buf,
?
????????????????????????????????????? sizeof(audio_buf));
?
????? if(audio_size < 0) {
?
???????
?
??????? audio_buf_size = 1024;
?
??????? memset(audio_buf, 0, audio_buf_size);
?
????? } else {
?
??????? audio_buf_size = audio_size;
?
????? }
?
????? audio_buf_index = 0;
?
??? }
?
??? len1 = audio_buf_size - audio_buf_index;
?
??? if(len1 > len)
?
????? len1 = len;
?
??? memcpy(stream, (uint8_t *)audio_buf + audio_buf_index, len1);
?
??? len -= len1;
?
?? ?stream += len1;
?
??? audio_buf_index += len1;
?
? }
?
}
?
解碼函數
?
//decode audio frame
?
int audio_decode_frame(AVCodecContext *aCodecCtx, uint8_t *audio_buf,
?
?????????????????????? int buf_size) {
?
?
? static AVPacket pkt;
?
? static uint8_t *audio_pkt_data = NULL;
?
? static int audio_pkt_size = 0;
?
?
? int len1, data_size;
?
?
? for(;;) {
?
??? while(audio_pkt_size > 0) {
?
????? data_size = buf_size;
?
????? len1 = avcodec_decode_audio2(aCodecCtx, (int16_t *)audio_buf, &data_size,
?
?????????????????????????????? ? audio_pkt_data, audio_pkt_size);
?
????? if(len1 < 0) {
?
???????
?
??????? audio_pkt_size = 0;
?
??????? break;
?
????? }
?
????? audio_pkt_data += len1;
?
????? audio_pkt_size -= len1;
?
????? if(data_size <= 0) {
?
???????
?
??????? continue;
?
????? }
?
?????
?
????? return data_size;
?
??? }
?
??? if(pkt.data)
?
????? av_free_packet(&pkt);
?
?
??? if(quit) {
?
????? return -1;
?
??? }
?
?
??? if(packet_queue_get(&audioq, &pkt, 1) < 0) {
?
????? return -1;
?
??? }
?
??? audio_pkt_data = pkt.data;
?
??? audio_pkt_size = pkt.size;
?
? }
?
}
?
//主函數
?
int main()
?
{
?
??????? AVFormatContext *pFormatCtx ;
?
??????? AVCodecContext *pCodecCtx,*aCodecCtx ;
?
??????? AVCodec *pCodec,*aCodec ;
?
??????? AVStream *st;
?
AVFrame *pFrame ;
?
??????? AVPacket packet ;
?
??????? struct SwsContext *img_convert_ctx=NULL;
?
??????? SDL_Event?????? event;
?
??????? uint8_t *buffer ;
?
??????? SDL_Rect rect ;
?
??????? char *filename="1.asf" ;
?
??????? int ret,i,videoStream,audioStream,numBytes,frameFinished ;
?
?
??????? ?//init sdl library with video and audio
?
??? ??? if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER))
?
??? ??? {
?
????????? fprintf(stderr, "Could not initialize SDL - %s/n", SDL_GetError());
?
????????? return -1 ;
?
??? ??? }
?
//init the format and codec library ?
?
??????? av_register_all() ;
?
?
??????? ret=av_open_input_file(&pFormatCtx,filename,NULL,0,NULL) ;
?
??????? if(ret<0)
?
??????? {
?
?????????????? printf("Error1:open input file failed!/n") ;
?
?????????????? return -1 ;
?
??????? }
?
//retrive stream information
?
??????? ret=av_find_stream_info(pFormatCtx) ;
?
??????? if(ret<0)
?
??????? {
?
?????????????? printf("Error2:find stream information failed!/n") ;
?
?????????????? return -1 ;
?
??????? }
?
?
//dump information about file onto standard error
?
??????? dump_format(pFormatCtx,0,filename,0) ;
?
?
??????? videoStream=-1 ;
?
??????? audioStream=-1 ;
?
??????? for(i=0; i < pFormatCtx->nb_streams; i++)
?
??????? ?{
?
? ???????????? if(pFormatCtx->streams[i]->codec->codec_type==CODEC_TYPE_VIDEO
?
???? ????????????????? &&videoStream < 0)
?
?????????????? {
?
??? ?????????????????? videoStream=i;
?
? ???????????? }
?
? ???????????? if(pFormatCtx->streams[i]->codec->codec_type==CODEC_TYPE_AUDIO &&
?
???? ????????????????? audioStream < 0)
?
?????????????? {
?
??? ?????????????????? audioStream=i;
?
? ???????????? }
?
??????? }
?
???????
?
//check whether find video stream and audio stream
?
??????? if(videoStream==-1)
?
??????? {
?
?????????????? printf("Error3:can't find video stream!/n") ;
?
?????????????? return -1 ;
?
??????? }
?
??????? if(audioStream==-1)
?
??????? {
?
?????????????? printf("Error4:can't find audio stream!/n") ;
?
?????????????? return -1 ;
?
??????? }
?
//get video codec context?????
?
??????? st=pFormatCtx->streams[videoStream] ;
?
??????? pCodecCtx=st->codec;??
?
//get audio codec contex
?
???????
?
??????? aCodecCtx=pFormatCtx->streams[audioStream] ->codec;??
?
???????
?
//find video codec
?
??????? pCodec=avcodec_find_decoder(pCodecCtx->codec_id) ;
?
??????? if(pCodec==NULL)
?
??????? {
?
?????????????? printf("Error5:can't find video decoder!/n") ;
?
?????????????? return -1 ;
?
??????? }
?
//open video decoder
?
??????? ret=avcodec_open(pCodecCtx,pCodec) ;
?
??????? if(ret<0)
?
??????? {
?
?????????????? printf("open video decoder failed!/n") ;
?
?????????????? return -1 ;
?
??????? }
?
?
// Allocate video frame
?
? ????? pFrame=avcodec_alloc_frame();
?
//init? audio codec context
?
??????? if(init_sdl_audio(aCodecCtx)<0)
?
??????? {
?
?????????????? printf("Error6:init sdl audio failed!/n") ;
?
?????????????? return -1 ;
?
??????? }
?
//find audio codec ???
?
??????? aCodec=avcodec_find_decoder(aCodecCtx->codec_id) ;
?
??????? if(aCodec==NULL)
?
??????? {
?
?????????????? printf("Error7:can't find audio decoder!/n") ;
?
?????????????? return -1 ;
?
??????? }
?
//open audio decoder
?
??????? ret=avcodec_open(aCodecCtx, aCodec);
?
??????? if(ret<0)
?
??????? {
?
?????????????? printf("Error8:open audio decoder failed!/n") ;
?
?????????????? return -1 ;
?
??????? }
?
?
?//init audio packet queue
?
? ????? packet_queue_init(&audioq);
?
? ????? SDL_PauseAudio(0);
?
//init overalay
?
??????? if(init_sdl(pCodecCtx->width,pCodecCtx->height)<0)
?
??????? {
?
?????????????? printf("Error9:init sdl library failed!/n") ;
?
?????????????? return -1 ;
?
??????? }
?
//不知道為什么
?
??????? ?url_set_interrupt_cb(decode_interrupt_cb);
?
//decode video and audio frame
?
??????? while(av_read_frame(pFormatCtx,&packet)>=0)
?
??????? {
?
?????????????? if(packet.stream_index==videoStream)
?
?????????????? {
?
//decode a frame
?
?????????????? ??????? avcodec_decode_video(pCodecCtx,pFrame,&frameFinished,packet.data,packet.size) ;
?
//finish or not ?
?
?????????????????????? if(frameFinished)
?
?????????????????????? {
?
?????????????????????????????? sdl_display((AVPicture *)pFrame,bmp,pCodecCtx->pix_fmt,pCodecCtx->width,pCodecCtx->height) ;??????
?
?????????????????????? }
?
?????????????? }else if(packet.stream_index==audioStream)
?
?????????????? {
?
?????????????????????? packet_queue_put(&audioq, &packet);
?
?
?????????????? }else
?
?????????????? {
?
?????????????????????? av_free_packet(&packet) ;
?
?????????????? }
?
?????????????? SDL_PollEvent(&event);
?
?????????????? switch(event.type)
?
?????????????? {
?
?????????????? ??? case SDL_QUIT:????
?
????????????????????????????????????? quit = 1;
?
?????????????????????? ????? ???????? SDL_Quit();
?
?????????????????????? ????? ???????? exit(0);
?
?????????????????????? ????? ???????? break;
?
?????????????? ??? default:
?
?????????????? ????? ???????? break;
?
?????????????? }
?
??????? }??????
?
// Free the YUV frame
?
??????? av_free(pFrame);
?
??????? ?
?
// Close the codec
?
??????? avcodec_close(pCodecCtx);
?
??????? ?
?
// Close the video file
?
??????? av_close_input_file(pFormatCtx);
?
??????? ?
?
??????? return 0;
?
?
}
?
?
但是這個程序沒有解決音視頻同步等問題,視頻數據顯示很快!