ffmpeg和SDL學習筆記(一)

來源:互聯網
上載者:User

根據ffmpeg官方網站上的例子程式開始學習ffmpeg和SDL編程。

SDL是一個跨平台的多媒體開發包。適用於遊戲,模擬器,播放器等應用軟體開發。支援linux 、win32 等作業系統。

主要應用:

視頻

  • 設定8bpp或更高的任意色彩深度的視頻模式。如果某個模式硬體不支援,可以選擇轉化為另一模式。
  • 直接寫入線性映像幀緩衝(framebuffer)。
  • 用色鍵值(colorkey)或者alpha混合屬性建立surface。
  • Surface的blit能自動的轉化為目標格式。blit是最佳化過的,並能使用硬體加速。x86平台上有針對MMX最佳化過的blit。
  • 硬體加速的blit和fill(填充)操作,如果硬體支援的話。

事件

  • 提供以下事件:

    • 應用程式的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) {
/* 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;
}
}
解碼函數
//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;
}
}
//主函數
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;

}

但是這個程式沒有解決音視頻同步等問題,視頻資料顯示很快!

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.