ffmpeg和SDL学习笔记(一)

根据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;

}

但是这个程序没有解决音视频同步等问题,视频数据显示很快!

 

 

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 148,827评论 1 317
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 63,511评论 1 266
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 99,318评论 0 218
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,108评论 0 189
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,112评论 1 266
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,387评论 1 185
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 30,905评论 2 283
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,657评论 0 177
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,144评论 0 223
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,744评论 2 225
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,100评论 1 236
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,565评论 2 222
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,041评论 3 216
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,769评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,301评论 0 178
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,173评论 2 239
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,313评论 2 242

推荐阅读更多精彩内容