本篇文章写一个视频复用器。输入一个pcm文件和一个yuv文件。pcm文件格式是f32le,单声道,采样率是48000。yuv文件格式是yuv420p,848x400。
读入两个文件,分别进行音频和视频编码,然后mux成目标文件,比如xxx.mp4。
代码中选择了编码2声道的音频,由于输入的pcm是单声道,所以代码中简单复制了pcm的数据到frame->data[1]中。
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
#include <libavutil/samplefmt.h>
#include <libavutil/timestamp.h>
typedef struct OutputStream {
AVStream *st;
AVCodecContext *enc_ctx;
/* 下一帧pts */
int64_t next_pts;
AVFrame *frame;
} OutputStream;
static AVFormatContext *ofmt_ctx;
static OutputStream video_st = {0}, audio_st = {0};
static FILE *in_file_v, *in_file_a;
static void log_packet(const AVPacket *pkt)
{
AVRational *time_base = &ofmt_ctx->streams[pkt->stream_index]->time_base;
printf("pts:%s pts_time:%s dts:%s dts_time:%s duration:%s duration_time:%s stream_index:%d\n",
av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, time_base),
av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, time_base),
av_ts2str(pkt->duration), av_ts2timestr(pkt->duration, time_base),
pkt->stream_index);
}
static void setup_video_stream()
{
int ret;
AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_H264);
if (!codec) {
printf("could not find %s encoder\n", avcodec_get_name(AV_CODEC_ID_H264));
exit(1);
}
//创建流
video_st.st = avformat_new_stream(ofmt_ctx, NULL);
if (!video_st.st) {
printf("avformat_new_stream fail \n");
exit(1);
}
//打开codec context
video_st.enc_ctx = avcodec_alloc_context3(codec);
if (!video_st.enc_ctx) {
printf("avcodec_alloc_context3 fail \n");
exit(1);
}
//设置编码参数
video_st.enc_ctx->bit_rate = 400000;
video_st.enc_ctx->width = 848;
video_st.enc_ctx->height = 480;
video_st.enc_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
video_st.st->time_base = (AVRational){1, 25};
video_st.enc_ctx->time_base = video_st.st->time_base;
video_st.enc_ctx->gop_size = 12;
video_st.enc_ctx->max_b_frames = 2;
//对于mp4/mkv/flv等格式,他们要求有global header,对于ts则没有
if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
video_st.enc_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
ret= avcodec_open2(video_st.enc_ctx, codec, NULL);
if (ret < 0) {
printf("could not open codec\n");
exit(1);
}
video_st.frame = av_frame_alloc();
if (!video_st.frame) {
exit(1);
}
video_st.frame->format = video_st.enc_ctx->pix_fmt;
video_st.frame->width = video_st.enc_ctx->width;
video_st.frame->height = video_st.enc_ctx->height;
//创建缓存,用于存放待编码数据
ret = av_frame_get_buffer(video_st.frame, 0);
if (ret < 0) {
printf("av_frame_get_buffer fail\n");
exit(1);
}
ret = avcodec_parameters_from_context(video_st.st->codecpar, video_st.enc_ctx);
if (ret < 0) {
printf("avcodec_parameters_from_context fail\n");
exit(1);
}
}
static void setup_audio_stream()
{
int ret, nb_samples;
AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_AAC);
if (!codec) {
printf("could not find %s encoder\n", avcodec_get_name(AV_CODEC_ID_AAC));
exit(1);
}
//创建流
audio_st.st = avformat_new_stream(ofmt_ctx, NULL);
if (!audio_st.st) {
printf("avformat_new_stream fail \n");
exit(1);
}
//打开codec context
audio_st.enc_ctx = avcodec_alloc_context3(codec);
if (!audio_st.enc_ctx) {
printf("avcodec_alloc_context3 fail \n");
exit(1);
}
//设置编码参数
audio_st.enc_ctx->bit_rate = 64000;
audio_st.enc_ctx->channel_layout = AV_CH_LAYOUT_STEREO;
audio_st.enc_ctx->channels = av_get_channel_layout_nb_channels(audio_st.enc_ctx->channel_layout);
audio_st.enc_ctx->sample_fmt = AV_SAMPLE_FMT_FLTP;
audio_st.enc_ctx->sample_rate = 48000;
audio_st.st->time_base = (AVRational){1, audio_st.enc_ctx->sample_rate};
audio_st.enc_ctx->time_base = audio_st.st->time_base;
//对于mp4/mkv/flv等格式,他们要求有global header,对于ts则没有
if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
audio_st.enc_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
ret= avcodec_open2(audio_st.enc_ctx, codec, NULL);
if (ret < 0) {
printf("could not open codec\n");
exit(1);
}
audio_st.frame = av_frame_alloc();
if (!audio_st.frame) {
exit(1);
}
if (audio_st.enc_ctx->codec->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE)
nb_samples = 10000;
else
nb_samples = audio_st.enc_ctx->frame_size;
audio_st.frame->format = audio_st.enc_ctx->sample_fmt;
audio_st.frame->channel_layout = audio_st.enc_ctx->channel_layout;
audio_st.frame->nb_samples = nb_samples;
//创建缓存,用于存放待编码数据
ret = av_frame_get_buffer(audio_st.frame, 0);
if (ret < 0) {
printf("av_frame_get_buffer fail\n");
exit(1);
}
ret = avcodec_parameters_from_context(audio_st.st->codecpar, audio_st.enc_ctx);
if (ret < 0) {
printf("avcodec_parameters_from_context fail\n");
exit(1);
}
}
static void encode_video(AVFrame *frame)
{
int ret;
AVPacket pkt = {0};
av_init_packet(&pkt);
/* 编码 */
ret = avcodec_send_frame(video_st.enc_ctx, frame);
if (ret < 0) {
fprintf(stderr, "Error sending a frame for encoding\n");
exit(1);
}
while (ret >= 0) {
ret = avcodec_receive_packet(video_st.enc_ctx, &pkt);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
return;
else if (ret < 0) {
fprintf(stderr, "Error during encoding\n");
exit(1);
}
av_packet_rescale_ts(&pkt, video_st.enc_ctx->time_base, video_st.st->time_base);
pkt.stream_index = video_st.st->index;
log_packet(&pkt);
//写入文件
ret = av_interleaved_write_frame(ofmt_ctx, &pkt);
if (ret < 0) {
printf("av_interleaved_write_frame fail\n");
exit(1);
}
}
}
//返回值:0编码未结束,1结束
static int write_video_frame(void)
{
int ret;
int width = video_st.frame->width;
int height = video_st.frame->height;
uint8_t yuv[width * height * 3 / 2];
if (!feof(in_file_v)) {
ret = av_frame_make_writable(video_st.frame);
if (ret < 0)
exit(1);
//清空
memset(yuv, 0, width * height * 3 / 2);
//读取Y、U、V
fread(yuv, 1, width * height * 3 / 2, in_file_v);
//设置Y、U、V数据到frame
/* Y */
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
video_st.frame->data[0][y * video_st.frame->linesize[0] + x] = yuv[y * width + x];
}
}
/* U and V */
for (int y = 0; y < height/2; y++) {
for (int x = 0; x < width/2; x++) {
video_st.frame->data[1][y * video_st.frame->linesize[1] + x] = yuv[width * height + y * width / 2 + x];
video_st.frame->data[2][y * video_st.frame->linesize[2] + x] = yuv[width * height * 5 / 4 + y * width / 2 + x];
}
}
//设置pts
video_st.frame->pts = video_st.next_pts++;
//编码
encode_video(video_st.frame);
return 0;
}else {
/* flush */
encode_video(NULL);
return 1;
}
}
static void encode_audio(AVFrame *frame)
{
int ret;
AVPacket pkt = {0};
av_init_packet(&pkt);
/* 编码 */
ret = avcodec_send_frame(audio_st.enc_ctx, frame);
if (ret < 0) {
fprintf(stderr, "Error sending a frame for encoding\n");
exit(1);
}
while (ret >= 0) {
ret = avcodec_receive_packet(audio_st.enc_ctx, &pkt);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
return;
else if (ret < 0) {
fprintf(stderr, "Error during encoding\n");
exit(1);
}
av_packet_rescale_ts(&pkt, audio_st.enc_ctx->time_base, audio_st.st->time_base);
pkt.stream_index = audio_st.st->index;
log_packet(&pkt);
//写入文件
ret = av_interleaved_write_frame(ofmt_ctx, &pkt);
if (ret < 0) {
printf("av_interleaved_write_frame fail\n");
exit(1);
}
}
}
//返回值:0编码未结束,1结束
static int write_audio_frame(void)
{
int ret;
int bytes_per_sample = av_get_bytes_per_sample(audio_st.enc_ctx->sample_fmt);
if (!feof(in_file_a)) {
ret = av_frame_make_writable(audio_st.frame);
if (ret < 0)
exit(1);
fread(audio_st.frame->data[0], 1, audio_st.frame->nb_samples * bytes_per_sample, in_file_a);
memcpy(audio_st.frame->data[1], audio_st.frame->data[0], audio_st.frame->nb_samples * bytes_per_sample);
//设置pts
audio_st.frame->pts = audio_st.next_pts;
audio_st.next_pts += audio_st.frame->nb_samples;
encode_audio(audio_st.frame);
return 0;
}else {
/* flush */
encode_audio(NULL);
return 1;
}
}
static void encode_and_mux()
{
const char *in_filename_v = "/Users/zhw/Desktop/resource/sintel_yuv420p_848x480.yuv";
const char *in_filename_a = "/Users/zhw/Desktop/resource/sintel_f32le_left_1_48000.pcm";
const char *out_filename = "/Users/zhw/Desktop/sintel.mp4";
AVOutputFormat *ofmt;
int ret;
//视频,音频是否编码结束
int encode_video = 1, encode_audio = 1;
in_file_v = fopen(in_filename_v, "rb");
if (!in_file_v) {
printf("file %s not exist\n", in_filename_v);
exit(1);
}
in_file_a = fopen(in_filename_a, "rb");
if (!in_file_a) {
printf("file %s not exist\n", in_filename_a);
exit(1);
}
//创建输出context
ret = avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out_filename);
if (ret < 0) {
printf("avformat_alloc_output_context2 fail\n");
exit(1);
}
ofmt = ofmt_ctx->oformat;
//设置视频流
setup_video_stream();
//设置音频流
setup_audio_stream();
//打印信息
av_dump_format(ofmt_ctx, 0, out_filename, 1);
/* 打开输出文件 */
if (!(ofmt->flags & AVFMT_NOFILE)) {
ret = avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE);
if (ret < 0) {
printf("avio_open fail\n");
exit(1);
}
}
//写文件头
ret = avformat_write_header(ofmt_ctx, NULL);
if (ret < 0) {
printf("avformat_write_header fail \n");
exit(1);
}
while (encode_video || encode_audio) {
if (encode_video &&
(!encode_audio || av_compare_ts(video_st.next_pts, video_st.enc_ctx->time_base,
audio_st.next_pts, audio_st.enc_ctx->time_base) <= 0)) {
encode_video = !write_video_frame();
} else {
encode_audio = !write_audio_frame();
}
}
av_write_trailer(ofmt_ctx);
//free
avcodec_free_context(&video_st.enc_ctx);
av_frame_free(&video_st.frame);
avcodec_free_context(&audio_st.enc_ctx);
av_frame_free(&audio_st.frame);
if (!(ofmt->flags & AVFMT_NOFILE))
avio_closep(&ofmt_ctx->pb);
avformat_free_context(ofmt_ctx);
}