0. 前言
在音视频开发任务中,编码和解码也是不可或缺的一环。
由于原始的视频和音频数据数据量太大,直接传输和存储的成本高,所以需要对原始的音视频数据进行压缩,这个过程便是编码。而解码则是反向过程,把压缩后的数据还原成可以播放的数据格式,计算机能够直接处理和应用的还是完整的未压缩数据。
音频和视频数据的编码原理和方式也不相同,因此也有着不同的编码方式,常见的音频编码标准有MP3,AAC等,视频方面常见的编码标准有H264、H265等。
前文我们讨论了封装和解封装任务,操作的对象是压缩数据帧和多媒体容器文件,今天我们主要讨论使用FFmpeg进行编码和解码功能的实现,即是压缩数据帧的生成和还原方式。
1. libavcodec介绍
libavcodec(libavc)是FFmpeg中专门用于处理编码和解码任务的子库,它提供了一套通用的编码/解码框架,并且内置了大量的编码和解码器实现,可以处理音频、视频、字幕流数据。
1.2 主要数据结构
首先需要了解一下使用libavcodec时常用的几个数据结构。风格上,这些数据结构和libavformat中设计的也是比较统一,AVCodec和AVCodecContext也是采用类似的“策略模式”来组织,分别表示编解码器行为的实现,以及编解码器运行过程中的状态信息。AVCodecParameters表示的是数据流的编码信息参数。AVFrame表示的是未经压缩过的原始帧数据。
1.2.1 AVCodecContext
AVCodecContext表示编解码器运行过程中的状态信息,包含不同编解码器用到的公共信息,由于编解码过程的复杂性以及兼容不同的编解码器,这个数据结构非常厚重。笔者对于很多字段也未能完全理解,我们挑一些字段来归纳理解其主要结构。
1.2.2 AVCodec
AVCodec表示的是FFmpeg具体支持的一个编解码器句柄。在编译FFmpeg代码时,我们使用configure来配置支持的编解码器,或者引入三方的编解码库如x264,x265等,所以FFmpeg所支持的所有编解码器在编译时便已经确定,在内部会使用一个列表来组织维护所有支持的编解码器。AVCodec便是用来描述具体的编解码器的结构。
可以看到,AVCodec结构体中内容并不多,而且并未包含任何的方法指针,那么FFmpeg是如何用它来描述不同的编解码器行为的呢?事实上,AVCodec只是提供给用户的公开接口,而FFmpeg内部使用
FFCodec结构体来描述具体的编解码器。FFCodec和AVCodec可以理解成C语言的封装形式(或接口实现),FFCodec的第一个字段便是AVCodec类型,可以同一个指针表示。因此,可以通过AVCodecContext保存的AVCodec*转换到具体的FFCodec*上。
static av_always_inline const FFCodec *ffcodec(const AVCodec *codec)
{
return (const FFCodec*)codec;
}
这里我们讨论一下AVCodec和AVCodecContext是如何关联起来的。
在使用avcodec_alloc_context3()函数创建AVCodecContext,或调用 avcodec_open2()打开编解码器时,可以传参设置一个具体的AVCodec,AVCodecContext内部使用codec字段来保存,其指向一个具体的编解码器对象FFCodec实现,如ff_h264_decoder、ff_aa_encoder等。
AVCodecContext的priv_data字段指向具体的编解码器对象使用到的私有数据结构,如ff_h264_decoder用到的H264Context。
1.2.3 AVCodecParameters
AVCodecParameters顾名思义,便是编解码器的配置参数,我们对于编解码器的很多设置也是放在这个结构体里面, 可以看见它里面的参数和AVCodecContext高度重合,但需要注意的是,这个结构体是“由AVStream保存”,我们在解封装后调用avformat_find_stream_info()可以拿到流对应的编解码信息,在需要解码时,可以设置这些参数给对应的AVCodecContext。
AVCodecParameters这个结构是给AVStream的成员codecpar,在编码时,调用avformat_write_header()也会根据这些参数写入header中。
解码时,可以用AVStream读到的codecpar来初始化解码器的AVCodecContext。
编码时,可以从“已经设置好,且open过后的编码器”的AVCodecContext拷贝至对应的AVStream的codecpar中。\在转码场景下
不要直接将源文件流的codecpar拷贝至输出文件流的(即使图像数据也没修改),否则会导致PPS设置错误而出现编码异常(只是单纯的转封装是可以这么操作),只要涉及到重新编码,都不要拷贝codecpar。
1.2.4 AVFrame
AVFrame表示的是解码之后的原始数据帧,整体的设计思路和AVPacket类似,也是引用计数结构。
2. 主要使用流程
2.1 解码
解码的流程如下:
1.首先需要查找解码所需要的解码器。一般在解封装后,使用avformat_find_stream_info()之后,AVStream中就可以获取对应的解码参数,可以通过avcodec_find_decoder()方法和对应的codecid确定解码器。
2.创建AVCodecContext,通过获取到的AVCodec来初始化解码器上下文,调用avcodec_alloc_context3()方法。
3.设置解码器参数。这里要对应的流的信息,设置解码器的解码参数,解码时如果已经获取了流的信息,可以直接调用avcodec_parameters_to_context()方法,将AStream的codecpar的内容拷贝至AVCodecContext中。
- 打开解码器,
avcodec_open2()。 - 解码循环。循环调用调用
avcodec_send_packet()送入读到的一帧压缩数据AVPacket,avcodec_receive_frame()。
两个tips
(1)并不是送入一帧Packet之后就一定能解出一帧Frame。编码器内部可能需要缓存多帧才开始解码。
(2)送入完所有的Packet之后,最后需要刷新解码器的缓存,调用avcodec_send_packet(context, NULL), 送入NULL。
- 释放资源,主要是关闭解码器
avcodec_close(),和释放解码器上下文avcodec_free_context()。
2.2 编码
编码的流程如下:
- 查找对应的编码器。这里根据需要编码的设置手动传入CodecID,或CodecName进行查找。
- 创建
AVCodecContext,通过获取到的AVCodec来初始化编码器上下文,调用avcodec_alloc_context3()方法。 - 设置编码器参数,这里应该根据需要手动设置。比如视频需要设置的fps,宽高,像素格式等这些数据,音频的采样率,采样点数据类型等,以及其他对于解码器的私有配置。
- 打开编码器,
avcodec_open2()。 - 设置流的编码参数。调用
avcodec_parameters_from_context(),将打开后的编码器上下文AVCodecContext中的内容拷贝到输出流的codecpar中。 - 编码循环。循环调用
avcodec_send_frame()送入原始数据帧,调用avcodec_receive_packet()获取压缩后的帧。 - 释放资源。主要是关闭编码器
avcodec_close(),和释放编码器上下文avcodec_free_context()。
3. 示例
3.1 编码
示例可以参考github.com/zzakafool/M… 目录:6_Encode。 这个示例会在代码中生成YUV全0的帧,并将其编码为10s的视频。
#include "encode.h"
extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/opt.h>
}
const int FRAME_WIDTH = 640;
const int FRAME_HEIGHT = 320;
const int TEST_FRAME_SIZE = 30 * 10;
void setDefaultColor(AVFrame *frame) {
// Y
for(int y = 0; y < frame->height; ++y) {
for(int x = 0; x < frame->width; ++x) {
frame->data[0][y * frame->linesize[0] + x] = 0;
}
}
// U & V
for(int y = 0; y < frame->height/2; ++y) {
for(int x = 0; x < frame->width/2; ++x) {
frame->data[1][y * frame->linesize[1] + x] = 0;
frame->data[2][y * frame->linesize[2] + x] = 0;
}
}
}
void encode(std::string dst) {
AVFormatContext* fmtCtx = nullptr;
int ret = avformat_alloc_output_context2(&fmtCtx, NULL, NULL, dst.c_str());
if(ret < 0) {
av_log(NULL, AV_LOG_ERROR, "allocate output format context failed\n");
return;
}
ret = avio_open(&fmtCtx->pb, dst.c_str(), AVIO_FLAG_WRITE);
if(ret < 0) {
av_log(NULL, AV_LOG_ERROR, "avio open failed\n");
return;
}
AVStream* strm = avformat_new_stream(fmtCtx, nullptr);
if(ret < 0) {
av_log(NULL, AV_LOG_ERROR, "add stream failed\n");
return;
}
strm->time_base = AVRational{1, 30};
strm->avg_frame_rate = AVRational{30, 0};
auto codec = avcodec_find_encoder(AV_CODEC_ID_H264);
if(codec == nullptr) {
av_log(NULL, AV_LOG_ERROR, "Cannot find codec h264\n");
return;
}
AVCodecContext* codecCtx = avcodec_alloc_context3(codec);
if(codecCtx == nullptr) {
av_log(NULL, AV_LOG_ERROR, "Cannot allocate context\n");
return;
}
codecCtx->bit_rate = 100000;
codecCtx->width = FRAME_WIDTH;
codecCtx->height = FRAME_HEIGHT;
codecCtx->time_base = AVRational{1, 30};
codecCtx->framerate = AVRational{30, 0};
codecCtx->gop_size = 12;
codecCtx->pix_fmt = AV_PIX_FMT_YUV420P;
av_opt_set(codecCtx->priv_data, "preset", "slow", 0);
av_opt_set(codecCtx->priv_data, "profile", "high", 0);
av_opt_set(codecCtx->priv_data, "level", "5.0", 0);
ret = avcodec_open2(codecCtx, codec, NULL);
if(ret < 0) {
av_log(NULL, AV_LOG_ERROR, "codec open failed\n");
return;
}
ret = avcodec_parameters_from_context(strm->codecpar, codecCtx);
if(ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Copy parameters from context failed\n");
return;
}
// write header
ret = avformat_write_header(fmtCtx, NULL);
if(ret < 0) {
av_log(NULL, AV_LOG_ERROR, "write header failed\n");
return;
}
AVFrame *frame = av_frame_alloc();
frame->width = FRAME_WIDTH;
frame->height= FRAME_HEIGHT;
frame->format= AV_PIX_FMT_YUV420P;
AVPacket *packet = av_packet_alloc();
ret = av_frame_get_buffer(frame, 0);
if(ret < 0) {
av_log(NULL, AV_LOG_ERROR, "allocate frame buffer failed\n");
return;
}
ret = av_frame_make_writable(frame);
if(ret < 0) {
av_log(NULL, AV_LOG_ERROR, "av frame make writable failed\n");
return;
}
// 绿色
setDefaultColor(frame);
for(int i = 0;i < TEST_FRAME_SIZE; ++i) {
frame->pts = i;
int ret = avcodec_send_frame(codecCtx, frame);
if(ret < 0) {
av_log(NULL, AV_LOG_ERROR, "send frame failed\n");
return;
}
while(ret >= 0) {
ret = avcodec_receive_packet(codecCtx, packet);
if(ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
break;
} else if(ret < 0) {
av_log(NULL, AV_LOG_ERROR, "receive packet failed\n");
return;
}
packet->stream_index = strm->index;
packet->dts = av_rescale_q(packet->dts, AVRational{1, 30}, strm->time_base);
packet->pts = av_rescale_q(packet->pts, AVRational{1, 30}, strm->time_base);
ret = av_interleaved_write_frame(fmtCtx, packet);
if(ret < 0) {
av_log(NULL, AV_LOG_ERROR, "write frame failed\n");
return;
}
av_packet_unref(packet);
}
}
ret = avcodec_send_frame(codecCtx, NULL);
if(ret < 0) {
av_log(NULL, AV_LOG_ERROR, "send NULL failed\n");
return;
}
while(ret >= 0) {
ret = avcodec_receive_packet(codecCtx, packet);
if(ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
break;
} else if(ret < 0) {
av_log(NULL, AV_LOG_ERROR, "receive packet failed\n");
return;
}
packet->stream_index = strm->index;
packet->dts = av_rescale_q(packet->dts, AVRational{1, 30}, strm->time_base);
packet->pts = av_rescale_q(packet->pts, AVRational{1, 30}, strm->time_base);
ret = av_interleaved_write_frame(fmtCtx, packet);
if(ret < 0) {
av_log(NULL, AV_LOG_ERROR, "write frame failed\n");
return;
}
av_packet_unref(packet);
}
ret = av_write_trailer(fmtCtx);
if(ret < 0) {
av_log(NULL, AV_LOG_ERROR, "write trailer failed\n");
return;
}
avcodec_close(codecCtx);
avio_closep(&fmtCtx->pb);
av_frame_free(&frame);
av_packet_free(&packet);
avformat_free_context(fmtCtx);
}
3.2 转码
示例可以参考github.com/zzakafool/M… 目录:7_Transcode。
这个示例简单地将封装和编码这些初始化操作代码封装了一下,避免维护太多的释放上下文的逻辑。
这里由于只在一个线程做转封装处理,所以解封装、解码、编码、封装,都是嵌套的逻辑,有点复杂。这里仅作为示例说明使用,一般把单独的子任务放在单独的线程中,代码会更加清晰。
#pragma once
#include <string>
#include <map>
#include <memory>
#include <functional>
template <typename T>
using sp = std::shared_ptr<T>;
extern "C" {
#include "libavformat/avformat.h"
#include "libavcodec/avcodec.h"
}
struct FormatItem {
std::string url; // 输入/输出url
AVFormatContext *fmtCtx = nullptr; // 输入/输出fmtCtx
std::map<int, AVCodecContext*> codecMap; // 编码/解码codecMap,streamid -> codecContext
static sp<FormatItem> openInputFormat(const std::string& url);
static sp<FormatItem> openOutputFormat(const std::string& url);
struct CodecSetting {
// 一般解码器会用到从Stream读到的codecParams
AVCodecParameters *codecParams = nullptr;
// 编码器的信息最好手动传入
enum AVCodecID codecID;
enum AVMediaType codec_type;
// video
int width = 0;
int height = 0;
AVRational framerate = {30, 1};
// audio
AVChannelLayout ch_layout;
int sample_rate = 0;
// common
int format = 0;
AVRational time_base = {1, 30};
};
bool openCodec(int streamid, const CodecSetting &codecSetting);
virtual ~FormatItem();
};
#include "formatItem.h"
extern "C" {
#include "libavutil/opt.h"
}
sp<FormatItem> FormatItem::openInputFormat(const std::string& url) {
sp<FormatItem> formatItem = std::make_shared<FormatItem>();
formatItem->url = url;
// 初始化解封装相关的组件
// 创建AVFormatContext和AVInputFormat
if(avformat_open_input(&formatItem->fmtCtx, url.c_str(), NULL, NULL) < 0) {
av_log(NULL, AV_LOG_ERROR, "Open src file failed.\n");
return nullptr;
}
// 读取输入多媒体文件的相关信息
if(avformat_find_stream_info(formatItem->fmtCtx, NULL) < 0) {
av_log(NULL, AV_LOG_ERROR, "Find stream info failed\n");
return nullptr;
}
return formatItem;
}
sp<FormatItem> FormatItem::openOutputFormat(const std::string& url) {
sp<FormatItem> formatItem = std::make_shared<FormatItem>();
formatItem->url = url;
// 初始化封装相关的组件
// 初始化AVFormatContext,同步文件名确定AVOutputFormat
if(avformat_alloc_output_context2(&formatItem->fmtCtx, NULL, NULL, url.c_str()) < 0) {
av_log(NULL, AV_LOG_ERROR, "Allocate output context failed.\n");
return nullptr;
}
// 打开输出的文件
if(!(formatItem->fmtCtx->oformat->flags & AVFMT_NOFILE)) {
auto ret = avio_open(&formatItem->fmtCtx->pb, url.c_str(), AVIO_FLAG_WRITE);
if(ret < 0) {
av_log(NULL, AV_LOG_ERROR, "IO Open failed.\n");
return nullptr;
}
}
return formatItem;
}
bool FormatItem::openCodec(int streamid, const CodecSetting &codecSetting) {
const AVCodec *codec = nullptr;
// 根据输入还是输出,寻找对应的编解码器AVCodec*
if(fmtCtx->iformat) {
codec = avcodec_find_decoder(codecSetting.codecID);
} else if(fmtCtx->oformat) {
codec = avcodec_find_encoder(codecSetting.codecID);
}
if(codec == nullptr) {
av_log(NULL, AV_LOG_ERROR, "Open Codec failed.\n");
return false;
}
// 创建AVCodecContext, 加入到codecMap中
AVCodecContext *codecCtx = avcodec_alloc_context3(codec);
if(codecCtx == nullptr) {
av_log(NULL, AV_LOG_ERROR, "Allocate Codec ctx failed.\n");
return false;
}
codecMap.insert({streamid, codecCtx});
if(codecSetting.codecParams) {
if(avcodec_parameters_to_context(codecCtx, codecSetting.codecParams) < 0) {
av_log(NULL, AV_LOG_ERROR, "copy parameters to context failed\n");
return false;
}
}
codecCtx->codec_type = codecSetting.codec_type;
if(codecCtx->codec_type == AVMEDIA_TYPE_VIDEO) {
codecCtx->pix_fmt = (AVPixelFormat)codecSetting.format;
codecCtx->width = codecSetting.width;
codecCtx->height = codecSetting.height;
} else if(codecCtx->codec_type == AVMEDIA_TYPE_AUDIO){
codecCtx->sample_fmt = (AVSampleFormat)codecSetting.format;
codecCtx->ch_layout = codecSetting.ch_layout;
codecCtx->sample_rate = codecSetting.sample_rate;
}
codecCtx->time_base = codecSetting.time_base;
codecCtx->framerate = codecSetting.framerate;
if(fmtCtx->oformat && codecCtx->codec->id == AV_CODEC_ID_H264) {
// codecCtx->pix_fmt = AV_PIX_FMT_YUV420P;
// codecCtx->mb_decision = 1;
codecCtx->bit_rate = 1000000;
av_opt_set(codecCtx->priv_data, "preset", "slow", 0);
av_opt_set(codecCtx->priv_data, "profile", "high", 0);
av_opt_set(codecCtx->priv_data, "level", "5.0", 0);
}
if(avcodec_open2(codecCtx, codec, NULL) < 0) {
av_log(NULL, AV_LOG_ERROR, "Open Codec Failed.\n");
return false;
}
return true;
}
FormatItem::~FormatItem()
{
for (auto pair : codecMap) {
avcodec_close(pair.second);
avcodec_free_context(&pair.second);
}
if (fmtCtx && fmtCtx->iformat) {
avformat_close_input(&fmtCtx);
fmtCtx = nullptr;
}
if (fmtCtx && fmtCtx->oformat) {
if (fmtCtx->pb) {
avio_closep(&fmtCtx->pb);
}
avformat_free_context(fmtCtx);
fmtCtx = nullptr;
}
if (fmtCtx) {
avformat_free_context(fmtCtx);
}
}
转码的主逻辑:
#include "transcode.h"
#include "formatItem.h"
// 输入是原始文件待解码的流
FormatItem::CodecSetting getDecodeSetting(AVStream *stream) {
FormatItem::CodecSetting decodeSetting;
decodeSetting.codecParams = stream->codecpar;
decodeSetting.codecID = stream->codecpar->codec_id;
decodeSetting.codec_type = stream->codecpar->codec_type;
decodeSetting.width = stream->codecpar->width;
decodeSetting.height = stream->codecpar->height;
decodeSetting.framerate = stream->avg_frame_rate;
decodeSetting.ch_layout = stream->codecpar->ch_layout;
decodeSetting.sample_rate = stream->codecpar->sample_rate;
decodeSetting.format = stream->codecpar->format;
decodeSetting.time_base = stream->time_base;
return decodeSetting;
}
// 输入是原始文件待解码的流,编码参数将用于新的转码流
FormatItem::CodecSetting getEncodeSetting(AVStream *oldStream) {
// 只用于转码,拷贝部分参数
FormatItem::CodecSetting encodeSetting;
encodeSetting.codecParams = nullptr;
encodeSetting.codecID = oldStream->codecpar->codec_id;
encodeSetting.codec_type = oldStream->codecpar->codec_type;
encodeSetting.width = oldStream->codecpar->width;
encodeSetting.height = oldStream->codecpar->height;
encodeSetting.framerate = oldStream->avg_frame_rate;
encodeSetting.ch_layout = oldStream->codecpar->ch_layout;
encodeSetting.sample_rate = oldStream->codecpar->sample_rate;
encodeSetting.format = oldStream->codecpar->format;
encodeSetting.time_base = oldStream->time_base;
return encodeSetting;
}
// 模拟解码后的图像处理方法,处理解码好的帧,处理完后准备重新编码
void handleVideoFrame(AVFrame* inFrame, AVFrame *outFrame) {
if(inFrame->format != AV_PIX_FMT_YUV420P) {
return;
}
// 模拟后处理解码后的图片的过程,拷贝一份帧
outFrame->width = inFrame->width;
outFrame->height = inFrame->height;
outFrame->format = inFrame->format;
av_frame_get_buffer(outFrame, 0);
av_frame_copy(outFrame, inFrame);
outFrame->pts = inFrame->pts;
//// 这里先注释掉,如果取消注释,就是写入全0,视频绿色
// av_frame_make_writable(outFrame);
//
// // Y
// for(int y = 0; y < outFrame->height; ++y) {
// for(int x = 0; x < outFrame->width; ++x) {
// outFrame->data[0][y * outFrame->linesize[0] + x] = 0;
// }
// }
// // U & V
// for(int y = 0; y < outFrame->height/2; ++y) {
// for(int x = 0; x < outFrame->width/2; ++x) {
// outFrame->data[1][y * outFrame->linesize[1] + x] = 0;
// outFrame->data[2][y * outFrame->linesize[2] + x] = 0;
// }
// }
}
void transcode(std::string src, std::string dst) {
sp<FormatItem> inItem = FormatItem::openInputFormat(src);
sp<FormatItem> outItem = FormatItem::openOutputFormat(dst);
if(inItem == nullptr || outItem == nullptr) {
av_log(NULL, AV_LOG_ERROR, "open input or output item failed.\n");
return;
}
// 从原文件的流信息,创建新的输出流
// 这里用一个map来记录 输入流id 和 输出流id 的对应关系
std::map<int, int> streamIdxMap;
for(int i = 0; i < inItem->fmtCtx->nb_streams; ++i) {
if(inItem->fmtCtx->streams[i]->codecpar->codec_id == AV_CODEC_ID_NONE) {
continue;
}
AVStream * strm = avformat_new_stream(outItem->fmtCtx, NULL);
strm->id = outItem->fmtCtx->nb_streams - 1;
strm->index = outItem->fmtCtx->nb_streams - 1;
streamIdxMap[i] = strm->index;
strm->time_base = inItem->fmtCtx->streams[i]->time_base;
// 打开解码器
FormatItem::CodecSetting decodeSetting = getDecodeSetting(inItem->fmtCtx->streams[i]);
inItem->openCodec(i, decodeSetting);
// 打开编码器
FormatItem::CodecSetting encodeSetting = getEncodeSetting(inItem->fmtCtx->streams[i]);
outItem->openCodec(strm->index, encodeSetting);
avcodec_parameters_from_context(strm->codecpar, outItem->codecMap[strm->index]);
av_log(NULL, AV_LOG_INFO, "src timebase : %d / %d\n", strm->time_base.num, strm->time_base.den);
}
AVPacket *inPacket = av_packet_alloc();
AVFrame *inFrame = av_frame_alloc();
AVPacket *outPacket = av_packet_alloc();
AVFrame *outFrame = av_frame_alloc();
// 写入头部信息
if(avformat_write_header(outItem->fmtCtx, NULL) < 0) {
av_log(NULL, AV_LOG_ERROR, "Write header failed.\n");
return;
}
// 注意,写完header之后,stream的timebase可能会发生改变。
for(int i = 0; i < outItem->fmtCtx->nb_streams; ++i) {
auto &strm = outItem->fmtCtx->streams[i];
av_log(NULL, AV_LOG_INFO, "new timebase : %d / %d\n", strm->time_base.num, strm->time_base.den);
}
// 逐帧写入
while(av_read_frame(inItem->fmtCtx, inPacket) >= 0) {
if(streamIdxMap.find(inPacket->stream_index) == streamIdxMap.end()) {
av_packet_unref(inPacket);
continue;
}
if(inItem->codecMap.find(inPacket->stream_index) == inItem->codecMap.end()) {
av_log(NULL, AV_LOG_ERROR, "Cannt find stream codec\n");
av_packet_unref(inPacket);
continue;
}
int inStreamId = inPacket->stream_index;
int outStreamId = streamIdxMap[inPacket->stream_index];
auto &oldStream = inItem->fmtCtx->streams[inStreamId];
auto &newStream = outItem->fmtCtx->streams[outStreamId];
int err = avcodec_send_packet(inItem->codecMap[inPacket->stream_index], inPacket);
if(err < 0) {
// 因为尽量消耗解码输出,所以应该不会有EAGAIN,所以这种情况应该无法恢复
av_log(NULL, AV_LOG_ERROR, "Error when decode send packet.\n");
return;
}
// 送进解码器后可以释放packet了
av_packet_unref(inPacket);
// while尽量消耗解码的输出
while(err >= 0) {
err = avcodec_receive_frame(inItem->codecMap[inStreamId], inFrame);
if(err == AVERROR(EAGAIN) || err == AVERROR_EOF) {
break;
} else if(err < 0) {
av_log(NULL, AV_LOG_ERROR, "Error when decode receive frame \n");
// 无法恢复的错误
return;
}
// 模拟解码完后的处理,这里音频直接拷贝不处理,视频过handleVideoFrame处理
if(oldStream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
av_frame_move_ref(outFrame, inFrame);
} else { // VIDEO
handleVideoFrame(inFrame, outFrame);
}
av_frame_unref(inFrame);
outFrame->pict_type = AV_PICTURE_TYPE_NONE;
outFrame->pts = av_rescale_q(outFrame->pts, oldStream->time_base, newStream->time_base);
outFrame->time_base = newStream->time_base;
err = avcodec_send_frame(outItem->codecMap[outStreamId], outFrame);
if(err < 0) {
// 因为尽量消耗编码输出,所以应该不会有EAGAIN,这种错误情况应该无法恢复
av_log(NULL, AV_LOG_ERROR, "Error when encode send frame %d \n", err);
return;
}
// 送入编码器后释放掉frame
av_frame_unref(outFrame);
// while尽量消耗编码输出
while(err >= 0) {
err = avcodec_receive_packet(outItem->codecMap[outStreamId], outPacket);
if(err == AVERROR(EAGAIN) || err == AVERROR_EOF) {
break;
} else if(err < 0) {
av_log(NULL, AV_LOG_ERROR, "Error when encode receive packet \n");
return;
}
outPacket->stream_index = outStreamId;
// 交叉写入音频和视频帧
if(av_interleaved_write_frame(outItem->fmtCtx, outPacket) < 0) {
av_log(NULL, AV_LOG_ERROR, "Error during write packet\n");
}
av_packet_unref(outPacket);
}
}
}
// 最后需要对所有解码器送NULL,解码完所有的帧
for(auto &p : inItem->codecMap) {
auto inStreamId = p.first;
auto codecCtx = p.second;
int outStreamId = streamIdxMap[inStreamId];
auto &oldStream = inItem->fmtCtx->streams[inStreamId];
auto &newStream = outItem->fmtCtx->streams[outStreamId];
int err = avcodec_send_packet(codecCtx, NULL);
if(err < 0) {
// 因为尽量消耗解码输出,所以应该不会有EAGAIN,所以这种情况应该无法恢复
av_log(NULL, AV_LOG_ERROR, "Error when decode send NULL.\n");
return;
}
// while尽量消耗解码的输出
while(err >= 0) {
err = avcodec_receive_frame(inItem->codecMap[inStreamId], inFrame);
if(err == AVERROR(EAGAIN) || err == AVERROR_EOF) {
break;
} else if(err < 0) {
av_log(NULL, AV_LOG_ERROR, "Error when decode receive frame \n");
// 无法恢复的错误
return;
}
// 模拟解码完后的处理,这里音频直接拷贝不处理,视频过handleVideoFrame处理
if(oldStream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
av_frame_move_ref(outFrame, inFrame);
} else { // VIDEO
handleVideoFrame(inFrame, outFrame);
}
av_frame_unref(inFrame);
outFrame->pict_type = AV_PICTURE_TYPE_NONE;
outFrame->pts = av_rescale_q(outFrame->pts, oldStream->time_base, newStream->time_base);
outFrame->time_base = newStream->time_base;
err = avcodec_send_frame(outItem->codecMap[outStreamId], outFrame);
if(err < 0) {
// 因为尽量消耗编码输出,所以应该不会有EAGAIN,这种错误情况应该无法恢复
av_log(NULL, AV_LOG_ERROR, "Error when encode send frame 2 \n");
return;
}
// 送入编码器后释放掉frame
av_frame_unref(outFrame);
// while尽量消耗编码输出
while(err >= 0) {
err = avcodec_receive_packet(outItem->codecMap[outStreamId], outPacket);
if(err == AVERROR(EAGAIN) || err == AVERROR_EOF) {
break;
} else if(err < 0) {
av_log(NULL, AV_LOG_ERROR, "Error when encode receive packet 2 \n");
return;
}
outPacket->stream_index = outStreamId;
// 交叉写入音频和视频帧
if(av_interleaved_write_frame(outItem->fmtCtx, outPacket) < 0) {
av_log(NULL, AV_LOG_ERROR, "Error during write packet\n");
}
av_packet_unref(outPacket);
}
}
}
// 对所有编码器送NULL
for(auto &p : outItem->codecMap) {
int outStreamId = p.first;
auto inStreamId = -1;
for(auto &p: streamIdxMap) {
if(p.second == outStreamId) {
inStreamId = p.first;
}
}
assert(inStreamId != -1);
auto &oldStream = inItem->fmtCtx->streams[inStreamId];
auto &newStream = outItem->fmtCtx->streams[outStreamId];
int err = avcodec_send_frame(outItem->codecMap[outStreamId], NULL);
if(err < 0) {
av_log(NULL, AV_LOG_ERROR, "Error when encode send NULL \n");
return;
}
// while尽量消耗编码输出
while(err >= 0) {
err = avcodec_receive_packet(outItem->codecMap[outStreamId], outPacket);
if(err == AVERROR(EAGAIN) || err == AVERROR_EOF) {
break;
} else if(err < 0) {
av_log(NULL, AV_LOG_ERROR, "Error when encode send frame \n");
return;
}
outPacket->stream_index = outStreamId;
// 交叉写入音频和视频帧
if(av_interleaved_write_frame(outItem->fmtCtx, outPacket) < 0) {
av_log(NULL, AV_LOG_ERROR, "Error during write packet\n");
}
av_packet_unref(outPacket);
}
}
// 写入尾部数据
if(av_write_trailer(outItem->fmtCtx) < 0) {
av_log(NULL, AV_LOG_ERROR, "Write trailer failed\n");
return;
}
av_packet_free(&inPacket);
av_packet_free(&outPacket);
av_frame_free(&inFrame);
av_frame_free(&outFrame);
}