1.首先需要先初始化一下,使用如下函数

1
av_register_all();

2.接着需要分配一个AVFormatContext,FFMPEG所有的操作都要通过这个AVFormatContext来进行

1
AVFormatContext *pFormatCtx = avformat_alloc_context();

3.接着调用打开视频文件

1
2
char *file_path = "E:in.mp4";
avformat_open_input(&pFormatCtx, file_path, NULL, NULL);

4.文件打开成功后就是查找文件中的视频流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    ///循环查找视频中包含的流信息,直到找到视频类型的流    
///便将其记录下来 保存到videoStream变量中
///这里我们现在只处理视频流 音频流先不管他
for (i = 0; i < pFormatCtx->nb_streams; i++) {
if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
videoStream = i;
}
}

///如果videoStream为-1 说明没有找到视频流
if (videoStream == -1) {
printf("Didn't find a video stream.
");
return -1;
}

5.现在根据视频流 打开一个解码器来解码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    ///查找解码器    
pCodecCtx = pFormatCtx->streams[videoStream]->codec;
pCodec = avcodec_find_decoder(pCodecCtx->codec_id);

if (pCodec == NULL) {
printf("Codec not found.
");
return -1;
}

///打开解码器
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
printf("Could not open codec.
");
return -1;
}

可以看出 我们可以直接根据查找到的视频流信息获取到解码器。

而且我们并不知道他实际用的是什么编码器。

这就是为什么一开始我们使用FFMPEG来操作,因为很多东西我们可以不关系。

6.现在开始读取视频了

1
2
3
4
5
6
7
8
int y_size = pCodecCtx->width * pCodecCtx->height;
AVPacket *packet = (AVPacket *) malloc(sizeof(AVPacket)); //分配一个packet
av_new_packet(packet, y_size); //分配packet的数据

if (av_read_frame(pFormatCtx, packet) < 0)
{
break; //这里认为视频读取完了
}

可以看出 av_read_frame读取的是一帧视频,并存入一个AVPacket的结构中。

7.前面我们说过 视频里面的数据是经过编码压缩的,因此这里我们需要将其解码

1
2
3
4
5
6
7
8
9
10
    if (packet->stream_index == videoStream) 
{
ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture,packet);

if (ret < 0) {
printf("decode error.
");
return -1;
}
}

8.基本上所有解码器解码之后得到的图像数据都是YUV420的格式,而这里我们需要将其保存成图片文件,因此需要将得到的YUV420数据转换成RGB格式,转换格式也是直接使用FFMPEG来完成

1
2
3
4
5
6
if (got_picture) {        
sws_scale(img_convert_ctx,
(uint8_t const * const *) pFrame->data,
pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data,
pFrameRGB->linesize);
}

至于YUV420和RGB图像格式的具体内容,这里不用去了解。这里只需要知道有这么个东西就行了, 对我们使用FFMPEG转换没有影响。

9.得到RGB数据之后就是直接写入文件了:

1
SaveFrame(pFrameRGB,     pCodecCtx->width,pCodecCtx->height,index++); //保存图片     if (index > 50) return 0; //这里我们就保存50张图片

10.SDL

SDL播放音频是通过回调函数的方式播放,且这个回调函数是在新的线程中运行,此回调函数固定时间激发一次,这个时间和要播放的音频频率有关系。
因此我们用FFMPEG读到一帧音频后,不是急着解码,而是将数据存入一个队列,等SDL回调函数激发的时候,从这个队列中取出数据,然后解码 播放。

11.音视频同步

现在我们就将视频和音频合并,并让声音和画面同步。
加入音频的部分就不做讲解了,这里主要讲下声音和视频同步的步骤。
首先刚开始播放的时候通过av_gettime()获取系统主时钟,记录下来。
以后便不断调用av_gettime()获取系统时钟 减去之前记录下的差值,便得到了一个视频播放了多久的实际时间。

对于视频的同步我们这样做:
从视频读取出的数据中包含一个pts的信息(每一帧图像都会带有pts的信息,pts就是播放视频的时候此图像应该显示的时间)。
这样只需要使用pts和前面获取的时间进行对比,pts比实际时间大,就调用sleep函数等一等,否则就直接播放出来。这样就达到了某种意义上的同步了。

而对于音频:
从前面使用SDL的例子,其实就能够发现一个现象:我们读取音频的线程差不多就是瞬间读完放入队列的,但是音频播放速度却是正常的,
并不是一下子播放完毕。因此可以看出,在音频播放上,SDL已经帮我们做了处理了,只需要将数据直接交给SDL就行了。

12.音频同步

1.读取视频的线程
2.解码视频的线程
3.解码播放音频的线程(这个是由SDL创建的)