FFMpeg视频编码实战和音频编码实战

视频编码流程

  1. avcodec_find_encoder:首先,通过指定的编码器名称(如H.264、MPEG-4等)找到对应的编码器。
  2. avcodec_alloc_context3:为找到的编码器分配一个上下文结构,这个结构包含了编码器所需的各种参数和状态信息。
  3. 设置编码上下文参数:配置编码器上下文的参数,包括分辨率、帧率、比特率等。
  4. avcodec_open2:打开编码器,准备开始编码工作。
  5. av_packet_alloc:分配一个AVPacket结构,用于存储编码后的数据包。
  6. av_frame_alloc:分配一个AVFrame结构,用于存储原始视频帧的数据。
  7. 设置Frame参数:配置AVFrame结构,包括设置像素格式、宽度、高度等参数。
  8. av_frame_get_buffer:为AVFrame分配缓冲区,用于存储视频帧的数据。
  9. 读文件:从输入源读取视频帧数据。如果读取成功,则继续处理;如果读取失败,则跳转到flush_encode步骤。
  10. avcodec_send_frame:将AVFrame发送给编码器进行编码。
  11. avcodec_receive_packet:从编码器接收编码后的数据包。
  12. 写文件:将编码后的数据包写入输出文件。
  13. flush_encode:当读取文件失败时,调用flush_encode函数清理编码器中的剩余数据。
  14. 释放资源:释放之前分配的所有资源,包括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;
}

音频编码实战

一、初始化阶段
  1. avcodec_find_encoder
    查找合适的音频编码器(如 AAC、MP3)。
  2. avcodec_alloc_context3
    分配编码器上下文。
  3. 设置编码器参数
    如采样率、通道数、比特率等。
  4. avcodec_open2
    打开编码器。
  5. avformat_alloc_output_context2
    创建输出格式上下文。
  6. avformat_new_stream
    新建输出音频流。
  7. avio_open
    打开输出文件。
  8. avformat_write_header
    写入文件头信息。
  9. swr_init
    初始化音频重采样(如果需要转换采样格式或通道布局)。
二、编码循环阶段
  1. av_frame_get_buffer
    获取一个音频帧缓冲区用于填充数据。
  2. 读取文件
    判断是否还有音频数据需要编码。
    • 如果有(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;
}
相关推荐
倒霉男孩5 小时前
HTML视频和音频
前端·html·音视频
邪恶的贝利亚7 小时前
FFMEPG常见命令查询
linux·运维·网络·ffmpeg
EasyDSS10 小时前
国标GB28181视频平台EasyCVR如何搭建汽车修理厂远程视频网络监控方案
网络·音视频
无证驾驶梁嗖嗖13 小时前
FFMPEG大文件视频分割传输教程,微信不支持1G文件以上
音视频
一个小猴子`14 小时前
FFMpeg音视频解码实战
ffmpeg·音视频
小白教程15 小时前
Python爬取视频的架构方案,Python视频爬取入门教程
python·架构·音视频·python爬虫·python视频爬虫·python爬取视频教程
Json____17 小时前
springboot 处理编码的格式为opus的音频数据解决方案【java8】
spring boot·后端·音视频·pcm·音频处理·解码器·opus
赤鸢QAQ17 小时前
ffpyplayer+Qt,制作一个视频播放器
python·qt·音视频
EasyNTS18 小时前
ONVIF/RTSP/RTMP协议EasyCVR视频汇聚平台RTMP协议配置全攻略 | 直播推流实战教程
大数据·网络·人工智能·音视频