今天分享音视频利用ffmpeg进行音视频解码方面的知识。
我们都知道视频的存储方式一般都是MP4、avi、FLV等封装的格式,如果需要在设备进行播放视频,就需要对其进行相应的处理,图片一般需要yuv或rgb格式的图片数据才能进行显示,音频需要pcm的格式数据进行播放,所以这时候就需要对封装格式的视频进行解码,而ffmpeg提供了一套接口可以对视频进行处理。下面是解码的流程图。
接下来就可以利用ffmpeg提供的接口进行解码了。
首先需要配置ffmpeg的环境,之前已经分享过环境搭建了,有问题的小伙伴可以查看我以前分享过的文章。
由于ffmpeg是用c语言编写的库,没有类的概念,所以都是利用上下文的方式来进行参数之间的传递。
首先需要调用 av_register_all()初始化所有的封装库,利用avformat_open_input()初始化解封装器。
一个视频一般都包含了视频流和音频流,这边介绍一下如何获取到这两个流的信息
一般有两种方式进行获取,一种是通过遍历的方式来进行获取,另外一种是通过ffmpeg提供的接口来进行获取。
这边的ic是调用上面的初始化解封装器后获得的解封装上下文,里面包含了所有流的信息,nb_streams是一个有多少个流。
遍历的方式:
//获取音视频流信息(遍历,函数获取)
for (int i = 0;i < ic->nb_streams;i++)
{
AVStream *as = ic->streams[i];
cout << "format=" << as->codecpar->format << endl;
cout << "codec_id=" << as->codecpar->codec_id << endl;
//音频 AVMEDIA_TYPE_AUDIO
if (as->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
{
audioStream = i;
cout << i <<"音频信息" << endl;
cout << "sample_rate=" << as->codecpar->sample_rate << endl;
cout << "channels=" << as->codecpar->channels << endl;
cout << "aduio fps=" << r2d(as->avg_frame_rate) << endl;
//一帧数据 单通道样本数
cout << "frame_size=" << as->codecpar->frame_size << endl;
//1024 * 2 * 2(根据存储格式,2个字节) fps = sample_rate/frame_size
}
//视频AVMEDIA_TYPE_VIDEO
else if (as->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
{
videoStream = i;
cout << i << "视频信息" << endl;
cout << "width=" << as->codecpar->width << endl;
cout << "height=" << as->codecpar->height << endl;
//帧率 fps 分数转换
cout << "video fps="<avg_frame_rate) << endl;
}
}
还有一种方式是通过av_find_best_stream来获取,通过传入要获取的流获取到对应的索引。
解封装完后就会变成对应的音频压缩数据和视频压缩数据,接下来要进行解码操作
主要调用api流程如下:
1、avcodec_find_decoder 找到视频解码器
2、avcodec_alloc_context3 创建解码器上下文
3、avcodec_parameters_to_context 配置解码器上下文参数
4、avcodec_open2 打开解码器上下文
音频也是类似的。
解码后图像格式和音频格式一般也不能在显示器和音频设备上进行播放,因为解码后的图像格式一般为yuv420p,显示器支持的一般为rgb格式的,所以需要进行像素格式转换,而解码后的音频格式为AV_SAMPLE_FMT_FLTP,即为32个字节的数据。
可能通过看自己的电脑配置就可以知道一般音频设备支持的都是16位或者24位大小的音频数据,所以也需要进行音频重采样,这边也主要是利用ffmpeg提供的api
像素格式转换用到的函数为
1、sws_getCachedContext,获取到格式转换上下文
2、sws_scale,进行像素格式转换
音频重采样用的函数为:
1、swr_alloc,获取到重采样上下文的空间
2、swr_alloc_set_opts,设置重采样参数
3、 swr_init,初始化重采样上下文
4、swr_convert,进行重采样
下面是源码分享,主要是打印了流程中的一些信息,并没有对图像和音频进行显示。
#include
#include
extern "C"
{
#include "libavformat/avformat.h"
#include "libavcodec/avcodec.h"
#include "libswscale/swscale.h"
#include "libswresample/swresample.h"
}
#pragma comment(lib,"avformat.lib")
#pragma comment(lib,"avutil.lib")
#pragma comment(lib,"avcodec.lib")
#pragma comment(lib,"swscale.lib")
#pragma comment(lib,"swresample.lib")
using namespace std;
static double r2d(AVRational r)
{
return r.num == 0 ? 0:(double)r.num / (double)r.den;
}
void XSleep(int ms)
{
chrono::milliseconds du(ms);
this_thread::sleep_for(du);
}
int main(int argc,char *argv[])
{
cout << "Test Demux FFmpeg.club" << endl;
const char *path = "test.mp4";
//初始化封装库
av_register_all();
//初始化网络库(可以打开rtsp rtmp http协议的流媒体视频)
avformat_network_init();
//注册解码器库
avcodec_register_all();
AVDictionary *opts = NULL;
//设置rtsp流以tcp协议打开
av_dict_set(&opts, "rtsp_transport", "tcp", 0);
//网络延时时间
av_dict_set(&opts, "max_delay", "500", 0);
//解封装上下文
AVFormatContext *ic = NULL;
int re = avformat_open_input(&ic,path,
0,// 0表示自动选择解封装器
&opts//参数设置,比如rtsp的延时时间
);
if (re != 0)
{
char buf[1024] = { 0 };
av_strerror(re,buf,sizeof(buf)-1);
cout << "open " << path << " failed!:" << buf << endl;
getchar();
return -1;
}
cout << "open " << path << " success!" << endl;
//获取流信息
re = avformat_find_stream_info(ic, 0);
//总时长 毫秒
int totalMs = ic->duration / (AV_TIME_BASE/1000);
cout << "totalMs = " << totalMs << endl;
//打印视频流详细信息
av_dump_format(ic, 0, path, 0);
//音视频索引,读取时区分音视频
int videoStream = 0;
int audioStream = 1;
//获取音视频流信息(遍历,函数获取)
for (int i = 0;i < ic->nb_streams;i++)
{
AVStream *as = ic->streams[i];
cout << "format=" << as->codecpar->format << endl;
cout << "codec_id=" << as->codecpar->codec_id << endl;
//音频 AVMEDIA_TYPE_AUDIO
if (as->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
{
audioStream = i;
cout << i <<"音频信息" << endl;
cout << "sample_rate=" << as->codecpar->sample_rate << endl;
cout << "channels=" << as->codecpar->channels << endl;
cout << "aduio fps=" << r2d(as->avg_frame_rate) << endl;
//一帧数据 单通道样本数
cout << "frame_size=" << as->codecpar->frame_size << endl;
//1024 * 2 * 2(根据存储格式,2个字节) fps = sample_rate/frame_size
}
//视频AVMEDIA_TYPE_VIDEO
else if (as->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
{
videoStream = i;
cout << i << "视频信息" << endl;
cout << "width=" << as->codecpar->width << endl;
cout << "height=" << as->codecpar->height << endl;
//帧率 fps 分数转换
cout << "video fps="<avg_frame_rate) << endl;
}
}
//获取视频流
videoStream=av_find_best_stream(ic, AVMEDIA_TYPE_VIDEO,-1,-1,NULL,0);
cout << "av_find_best_stream videoStream=" << videoStream << endl;
//
//找到视频解码器
AVCodec *vcodec=avcodec_find_decoder(ic->streams[videoStream]->codecpar->codec_id);
if (!vcodec)
{
cout << "can't find the codec id" << ic->streams[videoStream]->codecpar->codec_id << endl;
getchar();
return -1;
}
cout << "find the codec id" << ic->streams[videoStream]->codecpar->codec_id << endl;
//视频解码器打开
//创建解码器上下文
AVCodecContext *vc = avcodec_alloc_context3(vcodec);
//配置解码器上下文参数
avcodec_parameters_to_context(vc, ic->streams[videoStream]->codecpar);
//8线程解码
vc->thread_count = 8;
//打开解码器上下文
re = avcodec_open2(vc, 0, 0);
if (re != 0)
{
char buf[1024] = { 0 };
av_strerror(re, buf, sizeof(buf) - 1);
cout << "avcodec_open2 failed!:" << buf << endl;
getchar();
return -1;
}
cout << "video avcodec_open2 success" << endl;
///
//音频解码器打开
AVCodec *acodec = avcodec_find_decoder(ic->streams[audioStream]->codecpar->codec_id);
if (!acodec)
{
cout << "can't find the codec id" << ic->streams[audioStream]->codecpar->codec_id << endl;
getchar();
return -1;
}
cout << "find the codec id" << ic->streams[audioStream]->codecpar->codec_id << endl;
//创建解码器上下文
AVCodecContext *ac = avcodec_alloc_context3(acodec);
//配置解码器上下文参数
avcodec_parameters_to_context(ac, ic->streams[audioStream]->codecpar);
//8线程解码
ac->thread_count = 8;
//打开解码器上下文
re = avcodec_open2(ac, 0, 0);
if (re != 0)
{
char buf[1024] = { 0 };
av_strerror(re, buf, sizeof(buf) - 1);
cout << "avcodec_open2 failed!:" << buf << endl;
getchar();
return -1;
}
cout << "audio avcodec_open2 success" << endl;
//malloc AVPacket并初始化
AVPacket *pkt = av_packet_alloc();
AVFrame *frame = av_frame_alloc();
//创建像素格式和尺寸转换上下文
SwsContext *vctx = NULL;
unsigned char *rgb = NULL;
//音频重采样上下文初始化
SwrContext *actx = swr_alloc();
actx = swr_alloc_set_opts(actx,
av_get_default_channel_layout(2), //输出格式
AV_SAMPLE_FMT_S16, //输出样本格式
ac->sample_rate, //采样率
av_get_default_channel_layout(ac->channels), //输入格式
ac->sample_fmt,
ac->sample_rate,
0,0
);
re = swr_init(actx);
if (re != 0)
{
char buf[1024] = { 0 };
av_strerror(re, buf, sizeof(buf) - 1);
cout << "swr_init!:" << buf << endl;
getchar();
return -1;
}
unsigned char *pcm = NULL;
for (;;)
{
int re = av_read_frame(ic,pkt);
if (re != 0)
{
cout << "===============================================end==================================" << endl;
int ms = 3000;//三秒位置
long long pos = (double)ms / (double)1000 * r2d(ic->streams[pkt->stream_index]->time_base);
av_seek_frame(ic,videoStream,pos,AVSEEK_FLAG_BACKWARD|AVSEEK_FLAG_FRAME);
continue;
}
cout << "pkt->size"<size << endl;
//显示时间
cout << "pkt->pts" << pkt->pts << endl;
//转换成ms 实际的显示时间
cout << "pkt->pts ms=" << pkt->pts*(r2d(ic->streams[pkt->stream_index]->time_base)) << endl;
//解码时间
cout << "pkt->dts" << pkt->dts << endl;
AVCodecContext *cc = NULL;
if (pkt->stream_index == videoStream)
{
cout << "图像" << endl;
cc = vc;
}
else if (pkt->stream_index == audioStream)
{
cout << "音频" << endl;
cc = ac;
}
else
{
break;
}
///解码视频
//发送packet到解码线程,不占用cpu
//send传NULL后调用多次receive取出所有缓冲帧
re = avcodec_send_packet(cc,pkt);
if (re != 0)
{
char buf[1024] = { 0 };
av_strerror(re, buf, sizeof(buf) - 1);
cout << "avcodec_send_packet failed!:" << buf << endl;
continue;
}
for (;;)
{
//不占用cpu,从线程中获取解码接口,一次send可能对应多次receive
re = avcodec_receive_frame(cc, frame);
if (re != 0)break;
cout << "recv frame" << frame->format << " " << frame->linesize[0] << endl;
//视频
if (cc == vc)
{
vctx = sws_getCachedContext(
vctx, //传NULL会新创建
frame->width,frame->height, //输入的宽、高、格式
(AVPixelFormat)frame->format, //输入的格式yuv420P
frame->width,frame->height, //输出的宽高
AV_PIX_FMT_RGBA, //输出格式
SWS_BILINEAR, //尺寸变化的算法
0,0,0
);
if (vctx)
{
if (!rgb)rgb = new unsigned char[frame->height*frame->width * 4];
uint8_t *data[2] = { 0 };
data[0] = rgb;
int lines[2] = { 0 };
lines[0] = frame->width * 4;
re = sws_scale(vctx,
frame->data, //输入数据
frame->linesize, 0, //输入行大小
frame->height, //输入高度
data, //输出数据和大小
lines
);
cout << "sws_scale = " << re << endl;
}
}
else //音频
{
uint8_t *data[2] = { 0 };
if (!pcm)pcm = new uint8_t[frame->nb_samples * 2 * 2];
data[0] = pcm;
re = swr_convert(actx,
data, frame->nb_samples, //输出
(const uint8_t**)frame->data, frame->nb_samples //输入
);
cout << "swr_convert = " << re << endl;
}
}
//释放,引用计数-1 为0释放空间
av_packet_unref(pkt);
//XSleep(500);
}
av_frame_free(&frame);
av_packet_free(&pkt);
if (ic)
{
//释放封装上下文,并且把ic置为0
avformat_close_input(&ic);
}
getchar();
return 0;
}