本文实现了将视频文件解码成YUV文件和音频裸流PCM文件
解码过程:
1、打开音视频文件:avformat_open_input()
2、获取流信息:avformat_find_stream_info()
3、寻找解码器:avcodec_find_decoder()
4、打开解码器:avcodec_open2()
5、循环读取数据包:av_read_frame()
6、解码数据包:avcodec_send_packet()、avcodec_receive_frame()
解码视频流:
创建上下文环境结构体 AVFormatContext,打开视频文件,并获取流信息更新AVFormatContext
AVFormatContext* afc = avformat_alloc_context();
if (avformat_open_input(&afc, infile, NULL, NULL) != 0)
{
cout << "open video error " << endl;
return -1;
}
if (avformat_find_stream_info(afc, NULL) < 0)
{
cout << "find stream error" << endl;
return -1;
}
寻找视频流解码器并打开解码器。必须要调用avcodec_parameters_to_context()函数更新解码器上下文的信息,否则解码的时候会出错:picture size 0x0 is invalid
for (int i = 0; i < afc->nb_streams; ++i)
{
if (afc->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) //视频
{
videoflag = i;
vdecodec = avcodec_find_decoder(afc->streams[videoflag]->codecpar->codec_id);
vcc = avcodec_alloc_context3(vdecodec); //为解码器上下文创建内存并初始化
avcodec_parameters_to_context(vcc, afc->streams[i]->codecpar); //将流里面的数据更新到解码器上下文
if (avcodec_open2(vcc, vdecodec, NULL) < 0)
{
cout << "open video decodec error" << endl;
return -1;
}
}
循环读取数据包,并解码。注意这里的avcodec_send_packet()和avcodec_receive_frame()函数不是一对一的关系
int decodec_one_frame_video(AVCodecContext* codec_ctx, AVPacket* pkt, AVFrame* frame, FILE* output)
{
AVFrame* yuv = av_frame_alloc();
unsigned char* out_buffer = (unsigned char*)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P, codec_ctx->width, codec_ctx->height, 1));
av_image_fill_arrays(yuv->data, yuv->linesize, out_buffer, AV_PIX_FMT_YUV420P, codec_ctx->width, codec_ctx->height, 1); //填充yuv像素数据缓冲区,没有这步会导致sws_scale()失败
SwsContext* sws = sws_getContext(codec_ctx->width, codec_ctx->height, codec_ctx->pix_fmt,
codec_ctx->width, codec_ctx->height, AV_PIX_FMT_YUV420P,
SWS_BICUBIC, NULL, NULL, NULL);
int ret = 0;
if (ret = avcodec_send_packet(codec_ctx, pkt) != 0) //向解码器发送视频数据包
{
cout << "send video packet to decodec error, code:" << ret << endl;
return -1;
}
while (ret >= 0)
{
ret = avcodec_receive_frame(codec_ctx, frame); //接收解码后的数据帧
if (ret == 0)
{
sws_scale(sws, frame->data, frame->linesize, 0, codec_ctx->height, yuv->data, yuv->linesize); //将解码后帧的像素格式转换成YUV420
int size = codec_ctx->width * codec_ctx->height;
cout << "fmt = " << yuv->nb_samples << endl; //输出0,为AV_PIX_FMT_YUV420P
fwrite(yuv->data[0], 1, size, output); //y
fwrite(yuv->data[1], 1, size / 4, output); //u
fwrite(yuv->data[2], 1, size / 4, output); //v
cout << "write yuv one frame succeed" << endl;
}
else if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
{
cout << "receive video frame error, need again" << endl;
sws_freeContext(sws);
av_frame_free(&yuv);
return -2; //接收到的数据无效 需要重新读入
}
else
{
cout << "avcodec_receive_frame video error code:" << ret << endl;
sws_freeContext(sws);
av_frame_free(&yuv);
return -3; //解码错误,可选择直接退出程序
}
}
sws_freeContext(sws);
av_frame_free(&yuv);
return 0; //解码成功
}
//主函数调用
while (av_read_frame(afc, pkt) >= 0)
{
if (pkt->stream_index == videoflag) //视频解码
{
decodec_one_frame_video(vcc, pkt, frame, yuvfile);
}
}
//再次解码输出解码器中的数据,否则会丢失部分数据
decodec_one_frame_video(vcc, NULL, frame, yuvfile);/
avcodec_receive_frame()解码后调用了sws_scale()函数将图片格式转换成YUV420然后再写入文件,在调用sws_scale之前必须用sws_getContext初始化上下文环境,并设置转换时的参数。必须使用av_image_fill_arrays()填充目标图像的缓存区,不然会导致sws_scale()转换失败
unsigned char* out_buffer = (unsigned char*)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P,
vcc->width, vcc->height, 1));
av_image_fill_arrays(yuv->data, yuv->linesize, out_buffer, AV_PIX_FMT_YUV420P, vcc->width, vcc->height, 1); //填充yuv像素数据缓冲区,没有这步会导致sws_scale()失败
SwsContext* ssc = sws_getContext(vcc->width, vcc->height, vcc->pix_fmt,
vcc->width, vcc->height, AV_PIX_FMT_YUV420P,
SWS_BICUBIC, NULL, NULL, NULL);
音频解码和视频类似,如下的demo中实现了将视频文件解码成YUV文件和音频裸流PCM文件
demo:
/*
* 解码成yvu和pcm
*/
#include <iostream>
#include <cstdio>
using namespace std;
extern "C"
{
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
}
const char* infile = "1.mp4";
const char* yuvout = "out.yuv"; //输出纯视频流文件
const char* pcmout = "out.pcm"; //输出纯音频流文件
int decodec_one_frame_video(AVCodecContext* codec_ctx, AVPacket* pkt, AVFrame* frame, FILE* output)
{
AVFrame* yuv = av_frame_alloc();
unsigned char* out_buffer = (unsigned char*)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P, codec_ctx->width, codec_ctx->height, 1));
av_image_fill_arrays(yuv->data, yuv->linesize, out_buffer, AV_PIX_FMT_YUV420P, codec_ctx->width, codec_ctx->height, 1); //填充yuv像素数据缓冲区,没有这步会导致sws_scale()失败
SwsContext* sws = sws_getContext(codec_ctx->width, codec_ctx->height, codec_ctx->pix_fmt,
codec_ctx->width, codec_ctx->height, AV_PIX_FMT_YUV420P,
SWS_BICUBIC, NULL, NULL, NULL);
int ret = 0;
if (ret = avcodec_send_packet(codec_ctx, pkt) != 0) //向解码器发送视频数据包
{
cout << "send video packet to decodec error, code:" << ret << endl;
return -1;
}
while (ret >= 0)
{
ret = avcodec_receive_frame(codec_ctx, frame); //接收解码后的数据帧
if (ret == 0)
{
sws_scale(sws, frame->data, frame->linesize, 0, codec_ctx->height, yuv->data, yuv->linesize); //将解码后帧的像素格式转换成YUV420
int size = codec_ctx->width * codec_ctx->height;
cout << "fmt = " << yuv->nb_samples << endl; //输出0,为AV_PIX_FMT_YUV420P
fwrite(yuv->data[0], 1, size, output); //y
fwrite(yuv->data[1], 1, size / 4, output); //u
fwrite(yuv->data[2], 1, size / 4, output); //v
cout << "write yuv one frame succeed" << endl;
}
else if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) //接收到的数据无效 需要重新读入
{
cout << "receive video frame error, need again" << endl;
sws_freeContext(sws);
av_frame_free(&yuv);
return -2;
}
else
{
cout << "avcodec_receive_frame video error code:" << ret << endl;
sws_freeContext(sws);
av_frame_free(&yuv);
return -3;
}
}
sws_freeContext(sws);
av_frame_free(&yuv);
return 0;
}
int decodec_one_frame_audio(AVCodecContext *codec_ctx, AVPacket *pkt, AVFrame *frame, FILE* output)
{
int ret = 0;
cout << "frame fix = " << frame->format << endl;
if (ret = avcodec_send_packet(codec_ctx, pkt) != 0)
{
cout << "send audio packet to decodec error" << endl;
return -1;
}
while(ret >= 0)
{
ret = avcodec_receive_frame(codec_ctx, frame);
if (ret == 0)
{
int audio_buffersize = av_samples_get_buffer_size(NULL, codec_ctx->channels, codec_ctx->frame_size, AV_SAMPLE_FMT_S16P, 1); //获取音频数据大小
cout << "channels = " << codec_ctx->channels << "rate = " << codec_ctx->sample_rate << "sample_fmt = " << codec_ctx->sample_fmt << endl; //输出音频的通道数、采样率和采样格式(这里输出8,为AV_SAMPLE_FMT_FLTP)
fwrite(frame->data, 1, audio_buffersize,output);
cout << "write pcm one frame succeed" << endl;
}
else if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
{
cout << "receive audio frame error, need again" << endl;
return -2; //接收到的数据无效 需要重新读入
}
else
{
cout << "avcodec_receive_frame audio error code:" << ret << endl;
return -3; //解码错误,可选择直接退出程序
}
}
return 0; //解码成功
}
int main(int argc, char** argv)
{
AVFormatContext* afc = avformat_alloc_context(); //初始化上下文结构体AVFormatContext
if (avformat_open_input(&afc, infile, NULL, NULL) != 0)
{
cout << "open video error " << endl;
return -1;
}
if (avformat_find_stream_info(afc, NULL) < 0) //获取流信息,更新上下文结构AVFormatContext
{
cout << "find stream error" << endl;
return -1;
}
int videoflag = -1;
int audioflag = -1;
AVCodec* vdecodec, * adecodec;
AVCodecContext* vcc = NULL;
AVCodecContext* acc = NULL;
for (int i = 0; i < afc->nb_streams; ++i)
{
if (afc->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) //视频
{
videoflag = i;
vdecodec = avcodec_find_decoder(afc->streams[videoflag]->codecpar->codec_id);
vcc = avcodec_alloc_context3(vdecodec); //为解码器创建内存
avcodec_parameters_to_context(vcc, afc->streams[i]->codecpar); //更新解码器参数
if (avcodec_open2(vcc, vdecodec, NULL) < 0)
{
cout << "open video decodec error" << endl;
return -1;
}
}
else if (afc->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) //音频
{
audioflag = i;
adecodec = avcodec_find_decoder(afc->streams[audioflag]->codecpar->codec_id);
acc = avcodec_alloc_context3(adecodec);
avcodec_parameters_to_context(acc, afc->streams[i]->codecpar);
if (avcodec_open2(acc, adecodec, NULL) < 0)
{
cout << "open audio decodec error" << endl;
return -1;
}
}
}
if (videoflag == -1)
{
cout << "video stream not find" << endl;
return -1;
}
if (audioflag == -1)
{
cout << "audio stream not find" << endl;
return -1;
}
AVFrame* frame = av_frame_alloc();//视频解码后的frame
AVFrame* aframe = av_frame_alloc(); //音频解码后的frame
AVPacket* pkt = (AVPacket*)av_malloc(sizeof(AVPacket));
FILE* yuvfile = fopen(yuvout, "wb");
FILE* pcmfile = fopen(pcmout, "wb");
while (av_read_frame(afc, pkt) >= 0)
{
if (pkt->stream_index == videoflag) //视频解码
{
decodec_one_frame_video(vcc, pkt, frame, yuvfile);
}
else if (pkt->stream_index == audioflag) //音频解码
{
decodec_one_frame_audio(acc, pkt, aframe, pcmfile);
}
}
//再次解码输出解码器中的数据,否则会丢失部分数据
decodec_one_frame_video(vcc, NULL, frame, yuvfile);
decodec_one_frame_audio(acc, NULL, aframe, pcmfile);
//释放资源
fclose(yuvfile);
fclose(pcmfile);
av_packet_unref(pkt);
av_frame_free(&aframe);
av_frame_free(&frame);
avcodec_free_context(&vcc);
avcodec_free_context(&acc);
avformat_close_input(&afc);
return 0;
}