本文以aac数据的转码为例,其他编码格式类似,关键是明白FFmpeg转码的原理,具体实现大同小异,转码的流程如下:
主要使用的函数为:
- avformat_write_header()向目标文件写入文件头
- av_write_frame()/av_interleaved_write_frame()向目标文件写入数据
- av_write_trailer()结束写入目标文件
主要流程如下(以下称转码器为muxer):
- 首先调用avformat_alloc_context()来创建muxing上下文。
这里初始化了AVFormatContext(格式配置);第二个参数可以是一个AVFormat实例,用来决定视频/音频格式;第三个参数是一个描述格式的字符串,如“h264″、 "mpeg"等;第四个是filename,从它的扩展名可以推断应该使用的格式(在第三个参数为null的情况下)。
AVFormatContext* oc;
如转为dash
avformat_alloc_output_context2(&oc, NULL, "dash", "/home/dash/index.mpd");
转为hls
avformat_alloc_output_context2(&oc, NULL, "hls", "/home/hls/master.m3u8");
- 通过填充此上下文中的各个字段来设置muxer:必须设置oformat字段以选择要使用的muxer。
除非格式为AVFMT_NOFILE类型,否则必须将pb字段设置为打开的IO上下文,可以是从avio_open()返回的,也可以是自定义的。
除非格式是AVFMT_NOSTREAMS类型,否则必须使用avformat_new_stream()函数创建至少一个流。
此外应填写编解码器上下文信息,如编解码器类型、id和其他已知参数(如宽度/高度、像素或样本格式等)。stream timebase应设置为调用者希望用于此流的timebase。
以下为添加一个输出stream的代码:
/* Add an output stream. */
static void add_stream(OutputStream *ost, AVFormatContext *oc, AVCodec **codec,
enum AVCodecID codec_id) {
AVCodecContext *c;
int i;
/* find the encoder */
*codec = avcodec_find_encoder(codec_id);
if (!(*codec)) {
fprintf(stderr, "Could not find encoder for '%s'\n",
avcodec_get_name(codec_id));
exit(1);
}
ost->st = avformat_new_stream(oc, NULL);
if (!ost->st) {
fprintf(stderr, "Could not allocate stream\n");
exit(1);
}
ost->st->id = oc->nb_streams - 1;
c = avcodec_alloc_context3(*codec);
if (!c) {
fprintf(stderr, "Could not alloc an encoding context\n");
exit(1);
}
ost->enc = c;
switch ((*codec)->type) {
case AVMEDIA_TYPE_AUDIO:
c->sample_fmt =
(*codec)->sample_fmts ? (*codec)->sample_fmts[0] : AV_SAMPLE_FMT_FLTP;
c->bit_rate = 64000;
c->sample_rate = 44100;
if ((*codec)->supported_samplerates) {
c->sample_rate = (*codec)->supported_samplerates[0];
for (i = 0; (*codec)->supported_samplerates[i]; i++) {
if ((*codec)->supported_samplerates[i] == 44100)
c->sample_rate = 44100;
}
}
c->channels = av_get_channel_layout_nb_channels(c->channel_layout);
c->channel_layout = AV_CH_LAYOUT_STEREO;
if ((*codec)->channel_layouts) {
c->channel_layout = (*codec)->channel_layouts[0];
for (i = 0; (*codec)->channel_layouts[i]; i++) {
if ((*codec)->channel_layouts[i] == AV_CH_LAYOUT_STEREO)
c->channel_layout = AV_CH_LAYOUT_STEREO;
}
}
c->channels = av_get_channel_layout_nb_channels(c->channel_layout);
ost->st->time_base = AVRational{1, c->sample_rate};
break;
case AVMEDIA_TYPE_VIDEO:
c->codec_id = codec_id;
c->bit_rate = 400000;
/* Resolution must be a multiple of two. */
c->width = 352;
c->height = 288;
/* timebase: This is the fundamental unit of time (in seconds) in terms
* of which frame timestamps are represented. For fixed-fps content,
* timebase should be 1/framerate and timestamp increments should be
* identical to 1. */
ost->st->time_base = AVRational{1, STREAM_FRAME_RATE};
c->time_base = ost->st->time_base;
c->gop_size = 12; /* emit one intra frame every twelve frames at most */
c->pix_fmt = STREAM_PIX_FMT;
if (c->codec_id == AV_CODEC_ID_MPEG2VIDEO) {
/* just for testing, we also add B-frames */
c->max_b_frames = 2;
}
if (c->codec_id == AV_CODEC_ID_MPEG1VIDEO) {
/* Needed to avoid using macroblocks in which some coeffs overflow.
* This does not happen with normal video, it just happens here as
* the motion of the chroma plane does not match the luma plane. */
c->mb_decision = 2;
}
break;
default:
break;
}
/* Some formats want stream headers to be separate. */
if (oc->oformat->flags & AVFMT_GLOBALHEADER)
c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
}
- 在创建好stream以后可以通过av_opt_set_int()、av_opt_set_double()等进行转码设置:
AVFormatContext* oc;
av_opt_set(oc->priv_data, "preset", "superfast", 0); //设置preset
av_opt_set(oc->priv_data, "tune", "zerolatency", 0); //设置tune
//转码为dash时,同时生成m3u8索引文件
av_opt_set_int(oc->priv_data, "hls_playlist", 1, AV_OPT_SEARCH_CHILDREN);
//转码为dash时,设置切片长度,其中seg_duration单位为us
av_opt_set_int(oc->priv_data, "use_template ", 1, AV_OPT_SEARCH_CHILDREN);
av_opt_set_int(oc->priv_data, "seg_duration", 5000000, AV_OPT_SEARCH_CHILDREN);
//转码为hls时,设置切片长度
av_opt_set_int(oc->priv_data, "hls_time",10, AV_OPT_SEARCH_CHILDREN);
- 一般来说,实际音频转码过程中我们大多情况下需要对声道数(channels)采样率(sample rate)采样长度(AVSampleFormat)等等参数进行调整,这时候就不能直接对解码后的pcm编码成我们想要的编码格式,于是有了重采样。(如果是视频直接使用sws_scale对图像进行格式转换即可)
//ReSample:out_frame 为输出的重采样后的frame
int ReSample(AVFrame *in_frame, AVFrame *out_frame, int audio_index) {
int ret;
int max_dst_nb_samples = 4096;
int64_t src_nb_samples = in_frame->nb_samples;
out_frame->pts = in_frame->pts;
uint8_t *paudiobuf;
int decode_size, input_size, len;
if (pSwrCtx != NULL) {
out_frame->nb_samples = av_rescale_rnd(
swr_get_delay(pSwrCtx, ofmt_ctx->streams[audio_index]->codec->sample_rate) + src_nb_samples,
ofmt_ctx->streams[audio_index]->codec->sample_rate,
in_sample_rate,
AV_ROUND_UP);
ret = av_samples_alloc(
out_frame->data, &out_frame->linesize[0],
ofmt_ctx->streams[audio_index]->codec->channels, out_frame->nb_samples,
ofmt_ctx->streams[audio_index]->codec->sample_fmt, 0);
if (ret < 0) {
av_log(NULL, AV_LOG_WARNING,
"[%s.%d %s() Could not allocate samples Buffer\n", __FILE__,
__LINE__, __FUNCTION__);
return -1;
}
max_dst_nb_samples = out_frame->nb_samples;
uint8_t *m_ain[32];
setup_array(m_ain, in_frame,
in_sample_fmt,
src_nb_samples);
len = swr_convert(pSwrCtx, out_frame->data, out_frame->nb_samples,
(const uint8_t **)in_frame->data, src_nb_samples);
if (len < 0) {
char errmsg[BUF_SIZE_1K];
av_strerror(len, errmsg, sizeof(errmsg));
av_log(NULL, AV_LOG_WARNING, "[%s:%d] swr_convert!(%d)(%s)", __FILE__,
__LINE__, len, errmsg);
return -1;
}
} else {
printf("pSwrCtx with out init!\n");
return -1;
}
return 0;
}
- muxing上下文完全设置好后,调用avformat_write_header()来初始化muxer内部并写入文件头。
ret = avformat_write_header(oc, NULL);
- 接着反复调用av_interleaved_write_frame(),将数据发送到muxer。发送到muxer的包的计时信息必须在相应AVStream的时基中。
//将一帧数据写入输出文件中
static int write_frame(AVFormatContext *fmt_ctx, AVCodecContext *c,
AVStream *st, AVFrame *frame) {
int ret;
// send the frame to the encoder
ret = avcodec_send_frame(c, frame);
if (ret < 0) {
fprintf(stderr, "Error sending a frame to the encoder: %s\n",
wrap_av_err2str(ret));
exit(1);
}
while (ret >= 0) {
AVPacket pkt = {0};
ret = avcodec_receive_packet(c, &pkt);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
break;
else if (ret < 0) {
fprintf(stderr, "Error encoding a frame: %s\n", wrap_av_err2str(ret));
exit(1);
}
/* rescale output packet timestamp values from codec to stream timebase */
av_packet_rescale_ts(&pkt, c->time_base, st->time_base);
pkt.stream_index = st->index;
/* Write the compressed frame to the media file. */
log_packet(fmt_ctx, &pkt);
ret = av_interleaved_write_frame(fmt_ctx, &pkt);
av_packet_unref(&pkt);
if (ret < 0) {
//fprintf(stderr, "Error while writing output packet: %s\n",
// av_err2str(ret));
exit(1);
}
}
return ret == AVERROR_EOF ? 1 : 0;
}
写入所有数据后,调用av_write_trailer()刷新所有缓冲数据包并完成输出文件,然后关闭IO上下文(如果有),最后使用avformat_free_context()释放muxing上下文。
/* Write the trailer, if any. The trailer must be written before you
* close the CodecContexts open when you wrote the header; otherwise
* av_write_trailer() may try to use memory that was freed on
* av_codec_close(). */
av_write_trailer(oc);
/* Close each codec. */
if (have_video) close_stream(oc, &video_st);
if (have_audio) close_stream(oc, &audio_st);
if (!(fmt->flags & AVFMT_NOFILE)) /* Close the output file. */
avio_closep(&oc->pb);
/* free the stream */
avformat_free_context(oc);
转码结果:
播放截图(windows上这个.tmp文件才能播放,linux上直接播放.m3u8):
详细代码可到此处下载:https://download.csdn.net/download/qq_37984341/12553625