FFmpeg开发学习:音视频封装
1.基本流程
1.输入参数
输出文件路径 char *output
视频编码参数 AVCodecParameters *video_par
音频编码参数 AVCodecParameters *audio_par
数据包 AVPacket *packets[]
2.封装流程
(1)创建输出的上下文AVFormatContext指针
AVFormatContext *out_fmt_ctx = nullptr;
AVStream *video_stream = nullptr;
AVStream *audio_stream = nullptr;
//创建输出的上下文以及添加视频流和音频流
if(avformat_alloc_output_context2(&out_fmt_ctx, nullptr, nullptr, output_filename) < 0) {
fprintf(stderr, "falied to create output context\n");
return;
}
(2)添加AVStream的视频流和音频流
if(video_par) {
video_stream = avformat_new_stream(out_fmt_ctx, nullptr);
avcodec_parameters_copy(video_stream->codecpar, video_par);
video_stream->time_base = (AVRational){1, 1000};
}
if(audio_par) {
audio_stream = avformat_new_stream(out_fmt_ctx, nullptr);
avcodec_parameters_copy(audio_stream->codecpar, audio_par);
}
(3)打开输出的目标文件
//打开输出的文件
if(!(out_fmt_ctx->oformat->flags & AVFMT_NOFILE)) {
if(avio_open(&out_fmt_ctx->pb, output_filename, AVIO_FLAG_WRITE) < 0) {
fprintf(stderr, "can not open output file.\n" );
return;
}
}
(4)写入文件头
//写入头文件
if(avformat_write_header(out_fmt_ctx, nullptr) < 0) {
fprintf(stderr, "error occurred when writing header.\n");
}
(5)将所有的AVPacket写入AVStream
//写入所有packet
for(int i=0; i<packet_count; i++) {
AVPacket *pkt = packets[i];
//根据类型设置正确的time_base和stream_index
AVStream *out_stream = (pkt->stream_index==0) ? video_stream : audio_stream;
AVRational in_tb = (pkt->stream_index==0) ? (AVRational){1, 1000} : (AVRational){1, 48000};
//转换时间戳
av_packet_rescale_ts(pkt, in_tb, out_stream->time_base);
pkt->stream_index = out_stream->index;
if(av_interleaved_write_frame(out_fmt_ctx, pkt) < 0) {
fprintf(stderr, "failed to write packet.\n");
}
av_packet_unref(pkt);
}
(5.1)设置正确的time_base和stream_index
//根据类型设置正确的time_base和stream_index
AVStream *out_stream = (pkt->stream_index==0) ? video_stream : audio_stream;
AVRational in_tb = (pkt->stream_index==0) ? (AVRational){1, 1000} : (AVRational){1, 48000};
(6)转换时间戳
//转换时间戳
av_packet_rescale_ts(pkt, in_tb, out_stream->time_base);
pkt->stream_index = out_stream->index;
(7)写入文件尾
av_write_trailer(out_fmt_ctx);
(8)清理指针
if(!(out_fmt_ctx->oformat->flags & AVFMT_NOFILE)) {
avio_closep(&out_fmt_ctx->pb);
}
avformat_free_context(out_fmt_ctx);
基本上可以理解为,对于音视频封装的输入数据,需要视频编码参数以及音频编码参数两个参数,以及相关的数据帧
使用ffmpeg进行封装时,需要利用api写入相关格式的文件头以及文件尾,然后按顺序处理数据帧。
2.程序示例
模拟256个数据帧的音视频数据,并封装为mp4文件
extern "C"{
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/mathematics.h>
#include <libavutil/timestamp.h>
#include <libavutil/avutil.h>
#include <libavutil/mem.h>
}
#include <cstdio>
#include <cstdlib>
#include <cstring>
inline void mux_audio_video(const char *output_filename,
AVCodecParameters *video_par,
AVCodecParameters *audio_par,
AVPacket *packets[], int packet_count) {
AVFormatContext *out_fmt_ctx = nullptr;
AVStream *video_stream = nullptr;
AVStream *audio_stream = nullptr;
//创建输出的上下文以及添加视频流和音频流
if(avformat_alloc_output_context2(&out_fmt_ctx, nullptr, nullptr, output_filename) < 0) {
fprintf(stderr, "falied to create output context\n");
return;
}
if(video_par) {
video_stream = avformat_new_stream(out_fmt_ctx, nullptr);
avcodec_parameters_copy(video_stream->codecpar, video_par);
video_stream->time_base = (AVRational){1, 1000};
}
if(audio_par) {
audio_stream = avformat_new_stream(out_fmt_ctx, nullptr);
avcodec_parameters_copy(audio_stream->codecpar, audio_par);
}
//打开输出的文件
if(!(out_fmt_ctx->oformat->flags & AVFMT_NOFILE)) {
if(avio_open(&out_fmt_ctx->pb, output_filename, AVIO_FLAG_WRITE) < 0) {
fprintf(stderr, "can not open output file.\n" );
return;
}
}
//写入头文件
if(avformat_write_header(out_fmt_ctx, nullptr) < 0) {
fprintf(stderr, "error occurred when writing header.\n");
}
//写入所有packet
for(int i=0; i<packet_count; i++) {
AVPacket *pkt = packets[i];
//根据类型设置正确的time_base和stream_index
AVStream *out_stream = (pkt->stream_index==0) ? video_stream : audio_stream;
AVRational in_tb = (pkt->stream_index==0) ? (AVRational){1, 1000} : (AVRational){1, 48000};
//转换时间戳
av_packet_rescale_ts(pkt, in_tb, out_stream->time_base);
pkt->stream_index = out_stream->index;
if(av_interleaved_write_frame(out_fmt_ctx, pkt) < 0) {
fprintf(stderr, "failed to write packet.\n");
}
av_packet_unref(pkt);
}
//写入尾部信息
av_write_trailer(out_fmt_ctx);
//清理
if(!(out_fmt_ctx->oformat->flags & AVFMT_NOFILE)) {
avio_closep(&out_fmt_ctx->pb);
}
avformat_free_context(out_fmt_ctx);
return;
}
extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/mathematics.h>
#include <libavutil/timestamp.h>
#include <libavutil/avutil.h>
#include <libavutil/mem.h>
}
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include "function/mux_demux_case.h"
#define FAKE_PACKET_COUNT 10
#include "function/libavformat_case.h"
int main() {
av_log_set_level(AV_LOG_INFO);
const char *filename = "D:/Cpp/FFmpegUsingCase/test_output.mp4";
// 初始化视频参数
AVCodecParameters *vpar = avcodec_parameters_alloc();
vpar->codec_type = AVMEDIA_TYPE_VIDEO;
vpar->codec_id = AV_CODEC_ID_H264;
vpar->width = 1280;
vpar->height = 720;
vpar->format = AV_PIX_FMT_YUV420P;
vpar->bit_rate = 400000;
// 初始化音频参数
AVCodecParameters *apar = avcodec_parameters_alloc();
apar->codec_type = AVMEDIA_TYPE_AUDIO;
apar->codec_id = AV_CODEC_ID_AAC;
apar->sample_rate = 48000;
apar->ch_layout.nb_channels = 2;
apar->frame_size = 1024;
// apar->channel_layout = AV_CH_LAYOUT_STEREO;
apar->ch_layout.order = AV_CHANNEL_ORDER_NATIVE;
apar->ch_layout.u.mask = AV_CH_LAYOUT_STEREO;
apar->format = AV_SAMPLE_FMT_FLTP;
apar->bit_rate = 128000;
// 模拟一些 AVPacket
AVPacket *packet_array[FAKE_PACKET_COUNT];
for (int i = 0; i < FAKE_PACKET_COUNT; i++) {
AVPacket *pkt = av_packet_alloc();
pkt->data = (uint8_t *)av_malloc(100); // 模拟数据内容
pkt->size = 100;
pkt->pts = i * 40;
pkt->dts = i * 40;
pkt->duration = 40;
pkt->stream_index = i % 2; // 偶数为视频帧,奇数为音频帧
if (i % 5 == 0)
pkt->flags |= AV_PKT_FLAG_KEY;
packet_array[i] = pkt;
}
// 封装并输出到文件
mux_audio_video(filename, vpar, apar, packet_array, FAKE_PACKET_COUNT);
printf("finished: %s\n", filename);
// 清理参数结构体
avcodec_parameters_free(&vpar);
avcodec_parameters_free(&apar);
printAVFormatContextCase(filename);
return 0;
}