ffmpeg和SDL學(xué)習(xí)筆記(一)

根據(jù)ffmpeg官方網(wǎng)站上的例子程序開始學(xué)習(xí)ffmpeg和SDL編程。

SDL是一個跨平臺的多媒體開發(fā)包。適用于游戲,模擬器,播放器等應(yīng)用軟件開發(fā)。支持linux 、win32 等操作系統(tǒng)。

主要應(yīng)用:

視頻

設(shè)置8bpp或更高的任意色彩深度的視頻模式。如果某個模式硬件不支持,可以選擇轉(zhuǎn)化為另一模式。

直接寫入線性的圖像幀緩沖(framebuffer)。

用顏色鍵值(colorkey)或者alpha混合屬性創(chuàng)建surface。

Surface的blit能自動的轉(zhuǎn)化為目標(biāo)格式。blit是優(yōu)化過的,并能使用硬件加速。x86平臺上有針對MMX優(yōu)化過的blit。

硬件加速的blit和fill(填充)操作,如果硬件支持的話。

事件

提供以下事件:

應(yīng)用程序的visibility發(fā)生改變

鍵盤輸入

鼠標(biāo)輸入

用戶要求的退出

每種事件都能通過SDL_EventState()關(guān)閉或者打開。

事件經(jīng)由用戶指定的過濾函數(shù)再被加入到內(nèi)部的事件隊列。

線程安全的事件隊列。

音頻

設(shè)置8位和16位的音頻,單聲道或者立體聲,如果格式硬件不支持,可以選擇轉(zhuǎn)換。

由獨(dú)立的線程執(zhí)行音頻部分,并提供用戶回調(diào)(callback)機(jī)制。

設(shè)計上考慮到了客戶定制的軟混音器,但實(shí)際上在例程中就包含了一個完整的音頻/音樂輸出庫。

CD音頻

完整的CD音頻控制API

線程

簡單的線程創(chuàng)建API

用于同步的簡單的二進(jìn)制信號量(semaphores)

定時器

讀取已流逝的毫秒數(shù)。

等待指定的毫秒數(shù)。

設(shè)置一個10毫秒精度的周期性定時器。

字節(jié)序無關(guān)

偵測當(dāng)前系統(tǒng)的字節(jié)序

快速轉(zhuǎn)換數(shù)據(jù)的函數(shù)

讀寫指定字節(jié)序的數(shù)據(jù)

這里我們使用SDL作為音視頻輸出對象,ffmpeg完成音視頻的解碼。

像使用其他軟件包或者開發(fā)庫一樣,首先肯定要初始化相應(yīng)的庫,然后才能夠使用,初始化函數(shù)如下:

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有很多方法是實(shí)現(xiàn)視頻的輸出,但是YUV overlay是一種簡單而又常用的方法,具體使用方法是:

首先創(chuàng)建一個surface用來顯示視頻數(shù)據(jù),然后創(chuàng)建一個overlay,然后就可以通過overlay輸出視頻到surface

其創(chuàng)建過程如下:

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 ;

}

創(chuàng)建后就可以顯示視頻數(shù)據(jù)了,我對此進(jìn)行了簡單的封裝,如下:

//顯示函數(shù),提取一個完整的視頻幀后,就可以顯示此函數(shù)

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];

? ? //視頻格式轉(zhuǎn)化為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);

//設(shè)置顯示區(qū)域的位置和大小

rect.x = 0;

rect.y = 0;

rect.w = width;

rect.h = height;

//顯示視頻幀

SDL_DisplayYUVOverlay(bmp, &rect);

}

這樣在解碼出一幀數(shù)據(jù)后就可以通過調(diào)用此函數(shù)完成視頻的顯示了

視頻顯示搞定了,那么該輪到音頻輸出

要想輸出音頻,首先必須得打開音頻設(shè)備,SDL對音頻設(shè)備的打開和初始化已經(jīng)做好了封裝,我們通過調(diào)用SDL_OpenAudio 來打開和初始化音頻設(shè)備,通過結(jié)構(gòu)體 SDL_AudioSpec 設(shè)置相應(yīng)的參數(shù),然后將參數(shù)通過 SDL_OpenAudio 設(shè)置好設(shè)備,封裝如下:

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 ;

}

其他就和視頻一樣了,先分解出音頻流,然后根據(jù)音頻流找出解碼上下文,再根據(jù)解碼上下文找到解碼器,并打開了,接著就可以進(jìn)行解碼了。

但是我們不能想解碼視頻一樣,直接對音頻包進(jìn)行解碼,我們不斷從文件中的packet,同時SDL又要不斷的調(diào)用回調(diào)函數(shù),解決的辦法是創(chuàng)建一個互斥隊列,ffmpeg已經(jīng)為我們封裝了一個AVPacketList結(jié)構(gòu)體,我們需要對此進(jìn)行再次封裝如下:

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的個數(shù)。

對于一個隊列首先得有一個初始化函數(shù),完成初始化

void packet_queue_init(PacketQueue *q) {

? memset(q, 0, sizeof(PacketQueue));

? q->mutex = SDL_CreateMutex

();

? q->cond = SDL_CreateCond

();

}

很明顯這個初始化函數(shù)完成了隊列的內(nèi)存分配、互斥量和條件量的創(chuàng)建。

然后就是入隊和出隊的函數(shù)

//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為音頻處理創(chuàng)建了一個單獨(dú)的線程,線程中通過調(diào)用回調(diào)函數(shù)完成從包中解碼出音頻幀

然后再調(diào)用解碼函數(shù)將音頻幀解碼出來!

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) {

? ? ? /* We have already sent all our data; get more */

? ? ? audio_size = audio_decode_frame(aCodecCtx, audio_buf,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? sizeof(audio_buf));

? ? ? if(audio_size < 0) {

/* If error, output silence */

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;

? }

}

解碼函數(shù)

//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) {

/* if error, skip frame */

audio_pkt_size = 0;

break;

? ? ? }

? ? ? audio_pkt_data += len1;

? ? ? audio_pkt_size -= len1;

? ? ? if(data_size <= 0) {

/* No data yet, get more frames */

continue;

? ? ? }

? ? ? /* We have data, return it and come back for more later */

? ? ? 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;

? }

}

//主函數(shù)

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;

}

但是這個程序沒有解決音視頻同步等問題,視頻數(shù)據(jù)顯示很快!

?

?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 教程一:視頻截圖(Tutorial 01: Making Screencaps) 首先我們需要了解視頻文件的一些基...
    90后的思維閱讀 4,980評論 0 3
  • 在上一篇筆記中我們已經(jīng)完成了使用SDL播放聲音和視頻,聲音播放沒有什么問題,而視頻播放太快,很明顯視頻沒有同步。在...
    762683ff5d3d閱讀 1,418評論 0 1
  • void audioSpec_init(VideoState *is, Float64 sample_rate){...
    TakeCare_e819閱讀 743評論 0 0
  • // compatibility with newer API #if LIBAVCODEC_VERSION_IN...
    TakeCare_e819閱讀 1,719評論 0 0
  • 2年前剛接觸到理財,我抱著是為了讓財產(chǎn)不貶值、順便賺點(diǎn)小錢花花的心態(tài)。經(jīng)歷了1年的理財生活后,我知道了什么叫基金定...
    莫吟雪閱讀 331評論 0 1

友情鏈接更多精彩內(nèi)容