其实ffmpeg音视频的解码器调用流程在上一篇文章中已经讲述完成了,本文主要是将已有的程序进行了一定的封装处理,令其更符合工程规范。
先上代码:
#include<iostream>
extern "C"
{
#include "libavcodec/avcodec.h"
#include "include/libavformat/avformat.h"
#include "include/libswscale/swscale.h"
#include "include/libavdevice/avdevice.h"
}
using namespace std;
AVFormatContext* myFFMpegDemux(const char* path, AVFormatContext* ic);//打开视频文件
void myFFMpegShow(AVFormatContext* ic);//显示视频文件信息
const AVCodec* myFFMpegFindVideoCodec(AVFormatContext* ic, const AVCodec* vcodec, int* videoindex);//寻找视频流解码器
const AVCodec* myFFMpegFindAudioCodec(AVFormatContext* ic, const AVCodec* acodec, int* audioindex);//寻找音频流解码器
void myFFMpegOpenVideoCodec(AVFormatContext* ic, AVCodecContext *vc, int videoindex);//启动视频流解码器
void myFFMpegOpenAudioCodec(AVFormatContext* ic, AVCodecContext *ac, int audioindex);//启动音频流解码器
int main(int argc, char *argv[])
{
const char* path = "ds.mov";//记录视频源文件的路径,这里视频文件ds.mov直接放在项目工程里面了,所以可以直接用视频名称
//如果视频不在项目工程里面,路径的书写格式举例:D:\\code of visual studio\\ffmpegTest\\ffmpegTest\\ds.mov
//路径要使用“\\”,不然会被视为转义字符
cout << "TEST DEMUX" << endl;
//初始化封装库
//在新版本中av_register_all()被弃用了,可以根据代码里有无此函数判断ffmpeg版本
//初始化网络库(可以打开rtsp,rtmp,http协议的流媒体视频)
avformat_network_init();
//解封装上下文
AVFormatContext* ic = nullptr;//将其地址做为输入,会申请一块空间,将这块空间的地址赋给ic
//解封装上下文AVFormatContext,是存储音视频封装格式中包含信息的结构体。
//对视频进行解封装
ic = myFFMpegDemux(path,ic);
//获取并显示视频流与音频流的信息
myFFMpegShow(ic);
//初始化解码器信息
const AVCodec* vcodec = nullptr;
const AVCodec* acodec = nullptr;
//寻找合适的视频解码器与音频解码器
int videoindex = 0;
int audioindex = 0;
vcodec = myFFMpegFindVideoCodec(ic, vcodec,&videoindex);
acodec = myFFMpegFindAudioCodec(ic, acodec, &audioindex);
//创建解码器上下文
AVCodecContext *vc = avcodec_alloc_context3(vcodec);
myFFMpegOpenVideoCodec(ic, vc, videoindex);
AVCodecContext *ac = avcodec_alloc_context3(acodec);
myFFMpegOpenAudioCodec(ic, ac, audioindex);
//资源回收
if (ic) {//如果封装上下文仍存在
avformat_close_input(&ic);//释放资源,指针置零
ic = nullptr;
vcodec = nullptr;
acodec = nullptr;
vc = nullptr;
ac = nullptr;
}
return 0;
}
AVFormatContext* myFFMpegDemux(const char* path,AVFormatContext* ic) {
//对视频文件进行解封装操作
int re = avformat_open_input(&ic, path, 0, nullptr);//0表示自动选择解封装器,设置一个返回值知道有无错误
if (re != 0)//如果返回值不是0,说明打开出现错误
{
char buf[1024] = { 0 };
av_strerror(re, buf, sizeof(buf) - 1);//记录错误
cout << "open" << path << "failed!:" << buf << endl;//提示错误
return nullptr;
}
cout << "open " << path << " success!" << endl;//提示成功
return ic;
}
void myFFMpegShow(AVFormatContext* ic) {
avformat_find_stream_info(ic, 0);
//自行计算视频的总时长,以毫秒为单位,AV_TIME_BASE为1秒时长
int total = ic->duration / (AV_TIME_BASE / 1000);
cout << "total ms = " << total << endl;
//获取视频流的详细信息,包括视频流与音频流
av_dump_format(ic, 1, "2", 0);
for (int i = 0; i < ic->nb_streams; i++)//对视频中所有的流进行遍历
{
AVStream* as = ic->streams[i];
//音频
if (as->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
{
cout << i << "音频" << endl;
cout << "simple_rate" << as->codecpar->sample_rate << endl;
cout << "format" << as->codecpar->format << endl;
cout << "channels" << as->codecpar->channels << endl;
cout << "codec_id" << as->codecpar->codec_id << endl;
}
//视频
else if (as->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
{
cout << i << "视频" << endl;
cout << "simple_rate" << as->codecpar->sample_rate << endl;
cout << "format" << as->codecpar->format << endl;
cout << "channels" << as->codecpar->channels << endl;
cout << "codec_id" << as->codecpar->codec_id << endl;
}
}
}
const AVCodec* myFFMpegFindVideoCodec(AVFormatContext* ic, const AVCodec* vcodec,int* videoindex) {
//找到视频流在streams[i]中的编号i
*videoindex = av_find_best_stream(ic, AVMEDIA_TYPE_VIDEO, -1, -1, &vcodec, 0);
//找合适的视频解码器,得到流对应的解码器编号
vcodec = avcodec_find_decoder(ic->streams[*videoindex]->codecpar->codec_id);
//如果没有找到流对应的解码器,需要判断一下
if (!vcodec)
{
cout << "can't find the codec id" << ic->streams[*videoindex]->codecpar->codec_id << endl;
return nullptr;
}
cout << " find the video codec id" << ic->streams[*videoindex]->codecpar->codec_id << endl;
return vcodec;
}
const AVCodec* myFFMpegFindAudioCodec(AVFormatContext* ic,const AVCodec* acodec,int* audioindex) {
//找到音频流在streams[i]中的编号i
*audioindex = av_find_best_stream(ic, AVMEDIA_TYPE_AUDIO, -1, -1, &acodec, 0);
//找合适的音频解码器,得到流对应的解码器编号
acodec = avcodec_find_decoder(ic->streams[*audioindex]->codecpar->codec_id);
//如果没有找到流对应的解码器,需要判断一下
if (!acodec) {
cout << "can't find the codec id" << ic->streams[*audioindex]->codecpar->codec_id << endl;
return nullptr;
}
cout << " find the audio codec id" << ic->streams[*audioindex]->codecpar->codec_id << endl;
return acodec;
}
void myFFMpegOpenVideoCodec(AVFormatContext* ic, AVCodecContext *vc, int videoindex) {
//配置解码器上下文参数
avcodec_parameters_to_context(vc, ic->streams[videoindex]->codecpar);
//八线程解码
vc->thread_count = 8;
//打开解码器上下文
int re = avcodec_open2(vc, 0, 0);
if (re != 0)
{
char buf[1024] = { 0 };
av_strerror(re, buf, sizeof(buf) - 1);
cout << "open vedio codec failed!" << buf << endl;
return ;
}
cout << "video codec success!" << endl;
}
void myFFMpegOpenAudioCodec(AVFormatContext* ic, AVCodecContext *ac, int audioindex) {
//配置解码器上下文参数
avcodec_parameters_to_context(ac, ic->streams[audioindex]->codecpar);
//八线程解码
ac->thread_count = 8;
//打开解码器上下文
int re = avcodec_open2(ac, 0, 0);
if (re != 0)
{
char buf[1024] = { 0 };
av_strerror(re, buf, sizeof(buf) - 1);
cout << "open audio codec failed!" << buf << endl;
return;
}
cout << "audio codec success!" << endl;
}
以上的代码实现了视频文件的解封装,多媒体流的信息查看,视频流与音频流各自的解码器调用。
在程序中需要注意的是AVFormatContext* ic是贯穿程序始终的变量,因为它保留了视频文件的上下文全部信息,可以一定程度上视为程序文件本身,因此各个函数的传参中通常都有AVFormatContext* ic。
此外需要注意的是myFFMpegDemux函数实现了对视频文件的打开,在该过程中ic的内容是改变的(其初始值是nullptr),当一个变量在局部函数中进行改变处理时,我们需要将它的更新进行保存。对于ic这个指针而言,在局部函数中的更新方式主要为1.使用双指针2.将ic设为返回值。为减少代码复杂程度,本程序选择将ic设为返回值。其他的函数中ic仅仅是提供数值,本身没有改变,因此不需要考虑此步骤。
从代码中可以看出,视频流解码器的操作与音频流解码器的操作是镜像的,终其原因是ffmpeg对音频视频编码器型号进行了统一的编号,提供相同的接口,在程序中除了判断流的数组下标外其余都相同。

程序运行结果图如上图所示。

本文介绍了如何使用FFmpeg库进行音视频解码器的封装和调用,包括视频文件的解封装、多媒体流信息查看、解码器查找与启动。代码示例展示了从打开视频文件到初始化解码器上下文的完整过程,适用于音视频处理的工程实践。
1535

被折叠的 条评论
为什么被折叠?



