自定义播放器系列
第一章 视频渲染
第二章 音频(push)播放(本章)
第三章 音频(pull)播放
第四章 实现时钟同步
第五章 实现通用时钟同步
第六章 实现播放器
前言
使用ffmpeg解码音频并使用sdl播放,网上还是有一些例子的,大多都不是特别完善,比如打开音频设备、音频重采样、使用push的方式播放音频等,都是有不少细节需要注意处理。尤其是使用push的方式播放音频,流程很简单完全可以使用单线程实现,但是队列数据长度比较难控制控制。而且有时想要快速搭建一个demo时,总是要重新编写不少代码,比较不方便,所以在这里提供一个完善的例子,可以直接拷贝拿来使用。
一、ffmpeg解码
ffmpeg解码的流程是比较经典且通用的,基本上是文件、网络流、本地设备都是一模一样的流程。
1、打开输入流
首先需要打开输入流,输入流可以是文件、rtmp、rtsp、http等。
AVFormatContext* pFormatCtx = NULL;
const char* input="test.mp4";
//打开输入流
avformat_open_input(&pFormatCtx, input, NULL, NULL) ;
//查找输入流信息
avformat_find_stream_info(pFormatCtx, NULL) ;
2、查找音频流
因为是音频播放,所以需要找到输入流中的音频流。通过遍历判断codec_type 为AVMEDIA_TYPE_AUDIO值的流。音频流有可能有多个的,这里我们取第一个。
//视频流的下标
int audioindex = -1;
for (unsigned i = 0; i < pFormatCtx->nb_streams; i++)
if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
audioindex = i;
break;
}
3、打开解码器
通过输入流的信息获取到解码器参数然后查找到响应解码器,最后打开解码器即可。
AVCodecContext* pCodecCtx = NULL;
const AVCodec* pCodec = NULL;
//初始化解码上下文
pCodecCtx=avcodec_alloc_context3(NULL);
//获取解码参数
avcodec_parameters_to_context(pCodecCtx, pFormatCtx->streams[audioindex]->codecpar)
//查找解码器
pCodec=avcodec_find_decoder(pCodecCtx->codec_id);
//打开解码器
avcodec_open2(pCodecCtx, pCodec, &opts)
打开解码器时可以使用多线程参数优化解码速度。
AVDictionary* opts = NULL;
//使用多线程解码
if (!av_dict_get(opts, "threads", NULL, 0))
av_dict_set(&opts, "threads", "auto", 0);
4、解码
解码的流程就是读取输入流的包,对包进行解码,获取解码后的帧。
AVPacket packet;
AVFrame* pFrame = av_frame_alloc();
//读取包
while (av_read_frame(pFormatCtx, &packet) == 0)
{ //发送包
avcodec_send_packet(pCodecCtx, &packet);
//接收帧
while (avcodec_receive_frame(pCodecCtx, pFrame) == 0)
{
//取得解码后的帧pFrame
av_frame_unref(pFrame);
}
av_packet_unref(&packet);
}
解码有个细节是需要注意的,即当av_read_frame到文件尾结束后,需要再次调用avcodec_send_packet传入NULL或者空包flush出里面的缓存帧。下面是完善的解码流程: