转码
- 转码视频文件例子
extern "C"
{
#include <libavcodec/avcodec.h>
#include <libavfilter/buffersink.h>
#include <libavfilter/buffersrc.h>
#include <libavformat/avformat.h>
#include <libavutil/channel_layout.h>
#include <libavutil/opt.h>
#include <libavutil/pixdesc.h>
}
AVFormatContext *ifmt_ctx = nullptr;
AVFormatContext *ofmt_ctx = nullptr;
AVCodecContext *decodec_ctx = nullptr;
AVCodecContext *encodec_ctx = nullptr;
AVPacket *packet = nullptr;
AVFrame *dec_frame = nullptr;
AVPacket *enc_pkt = nullptr;
int videoStreamIndex = -1;
int ret = 0;
int openInfile(const char *infile);
int openOutfile(const char *outfile);
int decode_encode();
int encode(AVFrame *frame, AVPacket *enc_pkt);
void release();
int simpleTranscodeStart(const char *infile, const char *outfile)
{
if (0 != openInfile(infile))
{
return -1;
}
if (0 != openOutfile(outfile))
{
return -1;
}
if (0 != decode_encode())
{
return -1;
}
//写入文件尾
av_write_trailer(ofmt_ctx);
//释放资源
release();
return 0;
}
int openInfile(const char *infile)
{
unsigned int i;
//打开视音频文件,视音频码流的详细参数会保存在 ifmt_ctx 中。此接口也可用作拉流
//AVFormatContext是一个贯穿始终的数据结构,很多函数都要用到它作为参数。它是FFMPEG解封装(flv,mp4,rmvb,avi)功能的结构体
if ((ret = avformat_open_input(&ifmt_ctx, infile, NULL, NULL)) < 0)
{
av_log(NULL, AV_LOG_ERROR, "Cannot open input file\n");
return ret;
}
//该函数读取一部分视音频数据并且获得一些相关的信息,给每个媒体流(音频/视频)的AVStream结构体赋值
if ((ret = avformat_find_stream_info(ifmt_ctx, NULL)) < 0)
{
av_log(NULL, AV_LOG_ERROR, "Cannot find stream information\n");
return ret;
}
//nb_streams表示打开的音视频文件中的流数量,一般音视频文件中会包含视频流,音频流,字幕流。流的信息用AVStream结构体保存
//从AVStream中有一个变量AVCodecParameters *codecpar,可以通过此变量来打开对应的解码器
for (i = 0; i < ifmt_ctx->nb_streams; i++)
{
AVStream *stream = ifmt_ctx->streams[i];
//只解码音频
if (stream->codecpar->codec_type != AVMEDIA_TYPE_VIDEO)
{
continue;
}
videoStreamIndex = i;
//根据id查找解码器。还可以用avcodec_find_decoder_by_name()根据解码器名字查找解码器
//编译ffmpeg时决定了有多少可用的解码器
const AVCodec *dec = avcodec_find_decoder(stream->codecpar->codec_id);
if (!dec)
{
av_log(NULL, AV_LOG_ERROR, "Failed to find decoder for stream #%u\n", i);
return AVERROR_DECODER_NOT_FOUND;
}
//avcodec_alloc_context3()初始化一个解码器上下文。需要用avcodec_free_context()释放
//AVCodecContext 编解码器上下文,解码时需要传入这个作为参数
//音频和视频的编码解码,都是用这个结构体来保存编解码所需的参数。初始化时参数必须设置正确
decodec_ctx = avcodec_alloc_context3(dec);
if (!decodec_ctx)
{
av_log(NULL, AV_LOG_ERROR, "Failed to allocate the decoder context for stream #%u\n", i);
return AVERROR(ENOMEM);
}
//把AVStream中的编码器结构复制到解码器context中
ret = avcodec_parameters_to_context(decodec_ctx, stream->codecpar);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "Failed to copy decoder parameters to input decoder context for stream #%u\n", i);
return ret;
}
//把流的时间基赋值给解码器
//时间基和时间戳在播放和封装时很重要。时间基表示单位,时间戳表示时间。时间戳 * 时间基 = 实际时间(s)
decodec_ctx->pkt_timebase = stream->time_base;
/* Reencode video & audio and remux subtitles etc. */
decodec_ctx->framerate = av_guess_frame_rate(ifmt_ctx, stream, NULL);
//打开解码器
ret = avcodec_open2(decodec_ctx, dec, NULL);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "Failed to open decoder for stream #%u\n", i);
return ret;
}
} //for nb_streams
//打印输入视频文件的信息
av_dump_format(ifmt_ctx, 0, infile, 0);
return 0;
}
int openOutfile(const char *outfile)
{
AVStream *out_stream;
AVStream *in_stream;
const AVCodec *encoder;
int ret;
unsigned int i;
ofmt_ctx = NULL;
avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, outfile);
if (!ofmt_ctx)
{
av_log(NULL, AV_LOG_ERROR, "Could not create output context\n");
return AVERROR_UNKNOWN;
}
// for (i = 0; i < ifmt_ctx->nb_streams; i++)
{
//添加一个输出流
out_stream = avformat_new_stream(ofmt_ctx, NULL);
if (!out_stream)
{
av_log(NULL, AV_LOG_ERROR, "Failed allocating output stream\n");
return AVERROR_UNKNOWN;
}
in_stream = ifmt_ctx->streams[videoStreamIndex];
//查找编码器。原视频编码为x264,编码为AV_CODEC_ID_MJPEG
encoder = avcodec_find_encoder(AV_CODEC_ID_MJPEG /*decodec_ctx->codec_id*/);
if (!encoder)
{
av_log(NULL, AV_LOG_FATAL, "Necessary encoder not found\n");
return AVERROR_INVALIDDATA;
}
//创建编码器Context
encodec_ctx = avcodec_alloc_context3(encoder);
if (!encodec_ctx)
{
av_log(NULL, AV_LOG_FATAL, "Failed to allocate the encoder context\n");
return AVERROR(ENOMEM);
}
//编码器参数设置,这里除了编码格式,其它编码的参数都是从解码器复制过来
//实际使用时,编码器参数需要根据需求进行设置
if (encodec_ctx->codec_type == AVMEDIA_TYPE_VIDEO)
{
encodec_ctx->height = decodec_ctx->height;
encodec_ctx->width = decodec_ctx->width;
encodec_ctx->sample_aspect_ratio = decodec_ctx->sample_aspect_ratio;
/* take first format from list of supported formats */
if (encoder->pix_fmts)
encodec_ctx->pix_fmt = encoder->pix_fmts[0];
else
encodec_ctx->pix_fmt = decodec_ctx->pix_fmt;
/* video time_base can be set to whatever is handy and supported by encoder */
encodec_ctx->time_base = av_inv_q(decodec_ctx->framerate);
}
if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
encodec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
/* Third parameter can be used to pass settings to encoder */
ret = avcodec_open2(encodec_ctx, encoder, NULL);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "Cannot open video encoder for stream \n");
return ret;
}
//把编码器中的信息复制到视频流中
ret = avcodec_parameters_from_context(out_stream->codecpar, encodec_ctx);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "Failed to copy encoder parameters to output stream\n");
return ret;
}
out_stream->time_base = encodec_ctx->time_base;
}
av_dump_format(ofmt_ctx, 0, outfile, 1);
if (!(ofmt_ctx->oformat->flags & AVFMT_NOFILE))
{
//打开AVIOContext
//AVIOContext是ffmpeg中管理输入输出数据的结构体
ret = avio_open(&ofmt_ctx->pb, outfile, AVIO_FLAG_WRITE);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "Could not open output file '%s'", outfile);
return ret;
}
}
//写入文件头
ret = avformat_write_header(ofmt_ctx, NULL);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "Error occurred when opening output file\n");
return ret;
}
return 0;
}
int decode_encode()
{
packet = av_packet_alloc();
dec_frame = av_frame_alloc();
enc_pkt = av_packet_alloc();
if (!packet || !dec_frame || !enc_pkt)
{
return -1;
}
while (1)
{
//从输入文件(流)读取一帧数据,保存在AVPacket中
//AVPacket是存储压缩编码数据相关信息的结构体
if ((ret = av_read_frame(ifmt_ctx, packet)) < 0)
{
break;
}
if (packet->stream_index != videoStreamIndex)
{
av_packet_unref(packet);
continue;
}
//向解码器发送一帧需要解码的数据
//解码器不是喂入一帧数据就解码出一帧数据,可能是放入多帧才解码出来一帧数据,也可能一次出来多帧数据
ret = avcodec_send_packet(decodec_ctx, packet);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "Decoding failed\n");
break;
}
while (ret >= 0)
{
//尝试从解码器拿一帧解码后的数据AVFrame
//AVFrame保存解码后的图像数据(比如视频YUV,RGB;音频PCM)和一些相关的信息,如pts,dts,duration等
ret = avcodec_receive_frame(decodec_ctx, dec_frame);
//AVERROR_EOF:文件结束。AVERROR(EAGAIN):需要喂更多AVPacket才能解码出一帧
if (ret == AVERROR_EOF || ret == AVERROR(EAGAIN))
{
break;
}
else if (ret < 0)
{
return ret;
}
dec_frame->pts = dec_frame->best_effort_timestamp;
ret = encode(dec_frame, enc_pkt);
if (ret < 0)
{
return ret;
}
}
av_packet_unref(packet);
} //while(1)
//冲洗解码器,向解码器发一个空包,即表示flush
ret = avcodec_send_packet(decodec_ctx, NULL);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "Flushing decoding failed\n");
return -1;
}
while (ret >= 0)
{
ret = avcodec_receive_frame(decodec_ctx, dec_frame);
if (ret == AVERROR_EOF)
break;
else if (ret < 0)
return ret;
dec_frame->pts = dec_frame->best_effort_timestamp;
ret = encode(dec_frame, enc_pkt);
if (ret < 0)
return ret;
}
//冲洗编码器。向编码器发送一个空包
ret = encode(NULL, enc_pkt);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "Flushing encoder failed\n");
return ret;
}
return 0;
}
int encode(AVFrame *frame, AVPacket *enc_pkt)
{
av_packet_unref(enc_pkt);
//向编码器放入一帧AVFrame
//类似于解码器,不是放入一帧就会编码出来一帧
ret = avcodec_send_frame(encodec_ctx, frame);
if (ret < 0)
return ret;
while (ret >= 0)
{
//从编码器拿一帧编码后的数据
ret = avcodec_receive_packet(encodec_ctx, enc_pkt);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
return 0;
//这里需要设置帧的时间戳,时间基等参数,否则封装后的视频文件可能不会正常播放
enc_pkt->stream_index = videoStreamIndex;
enc_pkt->time_base = AVRational{1, 1000};
//av_packet_rescale_ts(enc_pkt, encodec_ctx->time_base, ofmt_ctx->streams[videoStreamIndex]->time_base);
av_log(NULL, AV_LOG_DEBUG, "Muxing frame\n");
/* mux encoded frame */
if (frame)
{
enc_pkt->duration = frame->duration;
}
//把编码后的帧输出
ret = av_interleaved_write_frame(ofmt_ctx, enc_pkt);
if (ret < 0)
return ret;
}
return 0;
}
void release()
{
if (ifmt_ctx)
avformat_free_context(ifmt_ctx);
if (ofmt_ctx)
avformat_free_context(ofmt_ctx);
if (decodec_ctx)
avcodec_free_context(&decodec_ctx);
if (encodec_ctx)
avcodec_free_context(&encodec_ctx);
if (packet)
av_packet_free(&packet);
if (dec_frame)
av_frame_free(&dec_frame);
if (enc_pkt)
av_packet_free(&enc_pkt);
}
** 几个流程中,主要的几个结构体 **
-
1.解封装和封装,主要用到AVFormatContext。在AVFormatContext中的AVStream结构,保存流的信息,主要是编码信息
-
2.解码和编码,主要用到AVCodecContext
-
3.解码后的数据保存AVFrame,编码后的数据保存AVPacket