视频编码流程
- avcodec_find_encoder:首先,通过指定的编码器名称(如H.264、MPEG-4等)找到对应的编码器。
- avcodec_alloc_context3:为找到的编码器分配一个上下文结构,这个结构包含了编码器所需的各种参数和状态信息。
- 设置编码上下文参数:配置编码器上下文的参数,包括分辨率、帧率、比特率等。
- avcodec_open2:打开编码器,准备开始编码工作。
- av_packet_alloc:分配一个AVPacket结构,用于存储编码后的数据包。
- av_frame_alloc:分配一个AVFrame结构,用于存储原始视频帧的数据。
- 设置Frame参数:配置AVFrame结构,包括设置像素格式、宽度、高度等参数。
- av_frame_get_buffer:为AVFrame分配缓冲区,用于存储视频帧的数据。
- 读文件:从输入源读取视频帧数据。如果读取成功,则继续处理;如果读取失败,则跳转到flush_encode步骤。
- avcodec_send_frame:将AVFrame发送给编码器进行编码。
- avcodec_receive_packet:从编码器接收编码后的数据包。
- 写文件:将编码后的数据包写入输出文件。
- flush_encode:当读取文件失败时,调用flush_encode函数清理编码器中的剩余数据。
- 释放资源:释放之前分配的所有资源,包括AVFrame、AVPacket和编码器上下文。
cpp
复制代码
//准备yuv文件
//引入FFMPEG库
#include<QDebug>
extern "C"{
#include<libavcodec/avcodec.h>
}
const char* inFileName = "D:\\output.yuv";
const char* outFileName = "output.h264";
/**
* @brief 编码一帧数据并写入输出文件
*
* @param codecContext 编码器上下文,包含编码器配置信息
* @param packet 存储编码后的数据包
* @param frame 待编码的原始帧数据
* @param outFile 输出文件指针
* @return int 成功返回0,失败返回错误码(负数)
*/
int encode(AVCodecContext* codecContext, AVPacket* packet, AVFrame* frame, FILE* outFile) {
// 1. 发送待编码的帧到编码器
int ret = avcodec_send_frame(codecContext, frame);
if (ret < 0) {
qDebug() << "avcodec_send_frame failed";
return -8; // 错误码-8:发送帧失败
}
// 2. 循环接收编码后的数据包
while (ret == 0) {
ret = avcodec_receive_packet(codecContext, packet);
// 2.1 检查是否需要更多输入或已结束
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
return 0; // 正常情况:需要更多数据或编码完成
}
// 2.2 检查其他错误
else if (ret < 0) {
qDebug() << "avcodec_receive_packet failed";
return -9; // 错误码-9:接收数据包失败
}
// 3. 成功接收到数据包,写入文件
if (ret == 0) {
fwrite(packet->data, 1, packet->size, outFile);
}
}
return 0; // 正常结束
}
int main(){
int ret = 0;
//准备编码器
const AVCodec* codec = nullptr;
AVCodecContext* codecContext = nullptr;
AVPacket* packet = nullptr;
AVFrame* frame = nullptr;
FILE* inFile = nullptr;
FILE* outFile = nullptr;
//查找264编码器
codec = avcodec_find_encoder(AV_CODEC_ID_H264);
if(!codec){
qDebug() << "avcodec_find_encoder failed";
return -1;
}
//分配编码器上下文
codecContext = avcodec_alloc_context3(codec);
if(!codecContext){
qDebug() << "avcodec_alloc_context3 failed";
return -2;
}
//设置上下文参数
codecContext->width = 1280; //设置视频的宽度为 1280 像素
codecContext->height = 720; //设置视频的高度为 720 像素
codecContext->time_base = AVRational{1, 25}; //设置时间基(time base)为 1/25 秒,这是时间戳的基本单位,表示每个时间单位代表 1/25 秒
codecContext->pix_fmt = AV_PIX_FMT_YUV420P; //设置像素格式为 YUV420P
codecContext->framerate = AVRational{25, 1}; //设置帧率为 25 帧/秒
//打开编码器
ret = avcodec_open2(codecContext, codec, NULL);
if(ret != 0){
qDebug() << "avcodec_open2 failed";
return -3;
}
packet = av_packet_alloc();
if(!packet){
qDebug() << "avcodec_open2 failed";
return -4;
}
frame = av_frame_alloc();
if(!frame){
qDebug() << "av_frame_alloc failed";
return -5;
}
//设置帧参数
frame->width = 1280;
frame->height = 720;
frame->format = AV_PIX_FMT_YUV420P;
// 申请frame的内存来存放帧数据
ret = av_frame_get_buffer(frame, 0);
if (ret < 0) { // 修正:检查返回值是否为负数(失败)
qDebug() << "av_frame_get_buffer failed, error:" << ret;
return -6;
}
//我们开始打开输入文件读数据
inFile = fopen(inFileName, "rb");
if(inFile == nullptr){
qDebug() << "open infile failed";
return -7;
}
//打开输出文件写数据
outFile = fopen(outFileName, "wb");
if(outFile == nullptr){
qDebug() << "open outFileName failed";
return -7;
}
//循环读数据编码
while(!feof(inFile)){
//frame能不能写,我们需要将yuv数据填充到frame,检查是否可写
ret = av_frame_is_writable(frame);
if(ret < 0){
//如果不可写,我们设置可写
ret = av_frame_make_writable(frame);
}
//从yuv文件读取
//y分量
fread(frame->data[0], 1, frame->width * frame->height, inFile);
//u分量
fread(frame->data[1], 1, frame->width * frame->height / 4, inFile);
//v分量
fread(frame->data[2], 1, frame->width * frame->height / 4, inFile);
//编码
encode(codecContext, packet, frame, outFile);
}
//如果没有数据了,我们退出
//flush_encode
encode(codecContext, packet, nullptr, outFile); //刷新编码器
qDebug()<<"编码完成";
//释放资源
av_packet_free(&packet);
av_frame_free(&frame);
avcodec_free_context(&codecContext);
fclose(inFile);
fclose(outFile);
return 0;
}
音频编码实战
一、初始化阶段
- avcodec_find_encoder
查找合适的音频编码器(如 AAC、MP3)。
- avcodec_alloc_context3
分配编码器上下文。
- 设置编码器参数
如采样率、通道数、比特率等。
- avcodec_open2
打开编码器。
- avformat_alloc_output_context2
创建输出格式上下文。
- avformat_new_stream
新建输出音频流。
- avio_open
打开输出文件。
- avformat_write_header
写入文件头信息。
- swr_init
初始化音频重采样(如果需要转换采样格式或通道布局)。
二、编码循环阶段
- av_frame_get_buffer
获取一个音频帧缓冲区用于填充数据。
- 读取文件
判断是否还有音频数据需要编码。
- 如果有(true):
- avcodec_send_frame:送入音频帧。
- avcodec_receive_packet:从编码器接收压缩数据。
- av_interleaved_write_frame:将编码后数据写入输出文件。
- 如果没有(false):
- flush_encode:刷新编码器内部缓存(送入空帧)。
- av_write_trailer:写入文件尾。
- 释放资源:关闭文件、释放上下文、帧、包等资源。
cpp
复制代码
#include <QDebug>
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswresample/swresample.h"
}
int main(){
char inputfile[] = "D:\\output.pcm";
char outputfile[] = "audio.aac";
//准备编码器
const AVCodec* avCodec = avcodec_find_encoder(AV_CODEC_ID_AAC);
if(!avCodec){
qDebug() << "avcodec_find_encoder failed";
return -1;
}
//准备编码上下文
AVCodecContext* avCodecContext = avcodec_alloc_context3(avCodec);
if(!avCodecContext){
qDebug() << "avcodec_alloc_context3 failed";
return -1;
}
//设置上下文参数
int ret=0;
avCodecContext->sample_rate = 44100; //表示音频的采样率为 44.1kHz(CD 音质标准)。
avCodecContext->channels = 2; //双声道
avCodecContext->sample_fmt = AV_SAMPLE_FMT_FLTP; //表示浮点型平面(Planar)采样格式。
avCodecContext->bit_rate = 64000; //表示编码后的音频比特率为 64kbps。
avCodecContext->channel_layout = AV_CH_LAYOUT_STEREO; //明确指定声道布局为立体声(左右声道)。
//打开解码器
ret = avcodec_open2(avCodecContext, avCodec,NULL);
if(ret<0){
qDebug() << "avcodec_open2 failed";
return -1;
}
//创建输出上下文
AVFormatContext* avFormatContext = NULL;
//初始化输出上下文
avformat_alloc_output_context2(&avFormatContext, NULL, NULL, outputfile);
if(!avFormatContext){
qDebug() << "avformat_alloc_output_context2 failed";
return -1;
}
// 新建输出音频流
AVStream* st = avformat_new_stream(avFormatContext, NULL);
st->codecpar->codec_tag = 0;
//这个流需要获取编码器参数
avcodec_parameters_from_context(st->codecpar, avCodecContext);
//打开输出文件
ret = avio_open(&avFormatContext->pb, outputfile, AVIO_FLAG_WRITE);
if(ret<0){
qDebug() << "avio_open failed";
return -1;
}
//写入文件头信息
avformat_write_header(avFormatContext, NULL);
SwrContext* swrContext = NULL;
// 设置参数, 1. 上下文 2.输出声道布局 4.输出采样率, 5.输入声道布局 6.输入样本格式 7.输入采样率 8.配音 9.日志
swrContext = swr_alloc_set_opts(swrContext, avCodecContext->channel_layout, avCodecContext->sample_fmt, avCodecContext->sample_rate,
AV_CH_LAYOUT_STEREO, AV_SAMPLE_FMT_S16, 44100,
0, 0);
if(!swrContext){
qDebug() << "swr_alloc_set_opts failed";
return -1;
}
// 初始化音频重采样
ret = swr_init(swrContext);
if(ret<0){
qDebug() << "swr_init failed";
return -1;
}
//准备frame
AVFrame* avFrame = av_frame_alloc();
avFrame->format = AV_SAMPLE_FMT_FLTP;
avFrame->channels=2;
avFrame->channel_layout = AV_CH_LAYOUT_STEREO;
avFrame->nb_samples=1024;
// 获取一个音频帧缓冲区用于填充数据
ret = av_frame_get_buffer(avFrame, 0);
if(ret<0){
qDebug() << "av_frame_get_buffer failed";
return -1;
}
//开始读pcm->frame
int readSize = avFrame->nb_samples*2*2; //双声道,float 16位,每次读取的大小
char* pcms = new char[readSize]; //申请内存
FILE* fp = fopen(inputfile, "rb");
//开始编码
for(;;){
AVPacket pkt;
av_init_packet(&pkt);
int len = fread(pcms,1,readSize, fp);
if(len <=0){
break;
}else{
//开始重采样
const uint8_t* data[1];
data[0] = (uint8_t*)pcms;
len = swr_convert(swrContext, avFrame->data, avFrame->nb_samples, data, avFrame->nb_samples);
if(len <=0){
qDebug() << "swr_convert failed";
break;
}
//重采样成功,我们开始编码数据
ret = avcodec_send_frame(avCodecContext, avFrame);
if(ret < 0){
qDebug() << "avcodec_send_frame failed";
continue;
}
//
ret = avcodec_receive_packet(avCodecContext, &pkt);
if(ret == 0){
av_interleaved_write_frame(avFormatContext, &pkt);
}
}
}
//写入结尾
av_write_trailer(avFormatContext);
//释放资源
fclose(fp);
avio_close(avFormatContext->pb);
avcodec_close(avCodecContext);
avcodec_free_context(&avCodecContext);
avformat_free_context(avFormatContext);
return 0;
}