FFMpeg音视频解码实战

音频解码

一、初始化阶段
  1. avformat_open_input
    打开输入媒体文件。
  2. avformat_find_stream_info
    读取媒体流信息,查找音频流。
  3. avcodec_find_decoder
    查找对应的解码器(如 AAC、MP3 解码器)。
  4. avcodec_alloc_context3
    分配解码器上下文。
  5. avcodec_open2
    打开解码器。
  6. av_packet_alloc
    分配 AVPacket 用于读取压缩数据。
  7. av_frame_alloc
    分配 AVFrame 用于接收解码后的原始音频帧。

二、解码循环阶段
  1. av_read_frame
    从输入文件中读取一个压缩包(Packet)。
  2. 读取判断
    • 如果读取成功(true):
      • avcodec_send_packet:将压缩数据送入解码器。
      • avcodec_receive_frame:从解码器取出解码后的音频帧。
      • 写文件:将解码后的音频帧保存或播放处理。
    • 如果读取失败(false):
      • 释放资源:关闭文件、释放上下文、帧和包。
cpp 复制代码
#include <QDebug>
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
}

int main()
{
    const char inFileName[] = "output.aac";  // 输入文件名(AAC编码的音频)
    const char outFileName[] = "test.pcm";   // 输出文件名(解码后的PCM原始音频)

    // 打开输出文件,用于写入PCM数据
    FILE *file = fopen(outFileName, "wb+");
    if (!file) {
        qDebug() << "Cannot open output file";
        return -1;
    }

    AVFormatContext *fmtCtx = avformat_alloc_context();  // 格式上下文
    AVCodecContext *codecCtx = NULL;                     // 编码器上下文
    AVPacket *pkt = av_packet_alloc();                   // 用于读取压缩数据包
    AVFrame *frame = av_frame_alloc();                   // 用于存储解码后的音频帧

    int aStreamIndex = -1;  // 音频流索引

    do {
        // 打开输入音频文件
        if (avformat_open_input(&fmtCtx, inFileName, NULL, NULL) < 0) {
            qDebug() << "Cannot open input file";
            return -1;
        }

        // 获取流信息
        if (avformat_find_stream_info(fmtCtx, NULL) < 0) {
            qDebug() << "Cannot find any stream in file";
            return -1;
        }

        // 查找音频流索引
        for (size_t i = 0; i < fmtCtx->nb_streams; i++) {
            if (fmtCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
                aStreamIndex = (int)i;
                break;
            }
        }
        if (aStreamIndex == -1) {
            qDebug() << "Cannot find audio stream";
            return -1;
        }

        // 获取音频编解码参数
        AVCodecParameters *aCodecPara = fmtCtx->streams[aStreamIndex]->codecpar;

        // 查找解码器
        const AVCodec *codec = avcodec_find_decoder(aCodecPara->codec_id);
        if (!codec) {
            qDebug() << "Cannot find any codec for audio";
            return -1;
        }

        // 分配解码器上下文
        codecCtx = avcodec_alloc_context3(codec);

        // 使用 codecpar 初始化 codecCtx
        if (avcodec_parameters_to_context(codecCtx, aCodecPara) < 0) {
            qDebug() << "Cannot alloc codec context";
            return -1;
        }

        // 设置时间基(用于同步处理)
        codecCtx->pkt_timebase = fmtCtx->streams[aStreamIndex]->time_base;

        // 打开解码器
        if (avcodec_open2(codecCtx, codec, NULL) < 0) {
            qDebug() << "Cannot open audio codec";
            return -1;
        }

        // ========== 解码循环 ==========
        while (av_read_frame(fmtCtx, pkt) >= 0) {
            if (pkt->stream_index == aStreamIndex) {
                // 向解码器送入压缩数据包
                if (avcodec_send_packet(codecCtx, pkt) >= 0) {
                    // 不断接收解码后的音频帧
                    while (avcodec_receive_frame(codecCtx, frame) >= 0) {
                        // 判断是否为平面格式(如FLTP)
                        if (av_sample_fmt_is_planar(codecCtx->sample_fmt)) {
                            int numBytes = av_get_bytes_per_sample(codecCtx->sample_fmt);
                            // PCM播放需要交错格式(LRLRLR...),手动交错保存
                            for (int i = 0; i < frame->nb_samples; i++) {
                                for (int ch = 0; ch < codecCtx->channels; ch++) {
                                    fwrite((char*)frame->data[ch] + numBytes * i, 1, numBytes, file);
                                }
                            }
                        }
                        // 非平面格式可以直接 fwrite(frame->data[0], ...)
                    }
                }
            }
            av_packet_unref(pkt);  // 清空包,为下一次读取做准备
        }

    } while (0);

    // 释放资源
    av_frame_free(&frame);
    av_packet_free(&pkt);
    avcodec_close(codecCtx);
    avcodec_free_context(&codecCtx);
    avformat_free_context(fmtCtx);
    fclose(file);

    return 0;
}

视频解码

cpp 复制代码
int main(int argc, char *argv[]) {
    const char *inputFileName = "C:\\Users\\edoYun\\Videos\\output.h264";  // 输入文件路径
    const char *outputFileName = "out.yuv";  // 输出文件路径

    // 打开输入文件
    AVFormatContext *formatCtx = avformat_alloc_context();  // 创建AVFormatContext对象
    if (avformat_open_input(&formatCtx, inputFileName, NULL, NULL) != 0) {
        qDebug() << "Error: could not open input file";  // 如果打开文件失败,输出错误信息
        return -1;
    }

    // 获取流信息
    if (avformat_find_stream_info(formatCtx, NULL) < 0) {
        qDebug() << "Error: could not find stream information";  // 如果找不到流信息,输出错误信息
        avformat_close_input(&formatCtx);  // 关闭输入文件
        return -1;
    }

    // 查找视频流
    int videoStream = -1;  // 初始化视频流索引
    for (int i = 0; i < formatCtx->nb_streams; i++) {  // 遍历所有流
        if (formatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {  // 找到视频流
            videoStream = i;
            break;
        }
    }

    if (videoStream == -1) {  // 如果没有找到视频流,输出错误信息
        qDebug() << "Error: could not find video stream";
        avformat_close_input(&formatCtx);  // 关闭输入文件
        return -1;
    }

    // 获取视频解码器
    AVCodecParameters *codecParams = formatCtx->streams[videoStream]->codecpar;  // 获取视频流的解码参数
    const AVCodec *codec = avcodec_find_decoder(codecParams->codec_id);  // 根据codec_id查找对应的解码器
    if (!codec) {
        qDebug() << "Error: unsupported codec";  // 如果找不到解码器,输出错误信息
        avformat_close_input(&formatCtx);  // 关闭输入文件
        return -1;
    }

    // 为解码器创建上下文
    AVCodecContext *codecCtx = avcodec_alloc_context3(codec);
    avcodec_parameters_to_context(codecCtx, codecParams);  // 将解码参数转换为上下文
    if (avcodec_open2(codecCtx, codec, NULL) < 0) {  // 打开解码器
        qDebug() << "Error: could not open codec";  // 如果解码器打开失败,输出错误信息
        avformat_close_input(&formatCtx);  // 关闭输入文件
        return -1;
    }

    // 打开输出文件
    FILE *outputFile = fopen(outputFileName, "wb");  // 以二进制方式打开输出文件
    if (!outputFile) {
        qDebug() << "Error: could not open output file";  // 如果打开输出文件失败,输出错误信息
        avcodec_close(codecCtx);  // 关闭解码器
        avformat_close_input(&formatCtx);  // 关闭输入文件
        return -1;
    }

    // 解码并输出 YUV420 文件
    AVFrame *frame = av_frame_alloc();  // 创建一个AVFrame来保存解码后的帧数据
    AVPacket packet;  // 存放压缩数据包
    av_init_packet(&packet);  // 初始化数据包
    int ret = 0;

    // 循环读取帧并解码
    while (av_read_frame(formatCtx, &packet) >= 0) {  // 循环读取数据包
        if (packet.stream_index == videoStream) {  // 如果数据包是视频流
            ret = avcodec_send_packet(codecCtx, &packet);  // 将数据包发送到解码器
            if (ret < 0) {
                qDebug() << "Error: sending packet to decoder";  // 如果发送失败,输出错误信息
                break;
            }

            // 获取解码后的帧
            while (ret >= 0) {
                ret = avcodec_receive_frame(codecCtx, frame);  // 从解码器接收帧
                if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)  // 如果需要更多数据或到达文件末尾,跳出循环
                    break;
                else if (ret < 0) {
                    qDebug() << "Error: receiving frame from decoder";  // 如果接收失败,输出错误信息
                    break;
                }

                // 将解码后的 YUV420 数据写入文件
                for (int i = 0; i < frame->height; i++) {
                    fwrite(frame->data[0] + i * frame->linesize[0], 1, frame->width, outputFile);  // 写入Y分量
                }
                for (int i = 0; i < frame->height / 2; i++) {
                    fwrite(frame->data[1] + i * frame->linesize[1], 1, frame->width / 2, outputFile);  // 写入U分量
                }
                for (int i = 0; i < frame->height / 2; i++) {
                    fwrite(frame->data[2] + i * frame->linesize[2], 1, frame->width / 2, outputFile);  // 写入V分量
                }
            }
        }
        av_packet_unref(&packet);  // 释放数据包
    }

    // 释放资源
    fclose(outputFile);  // 关闭输出文件
    av_frame_free(&frame);  // 释放帧
    avcodec_close(codecCtx);  // 关闭解码器
    avformat_close_input(&formatCtx);  // 关闭输入文件

    qDebug() << "Finished decoding and writing YUV420 file";  // 解码并写入文件完成
}
相关推荐
邪恶的贝利亚2 小时前
FFMEPG常见命令查询
linux·运维·网络·ffmpeg
一个小猴子`3 小时前
FFMpeg视频编码实战和音频编码实战
ffmpeg·音视频
EasyDSS5 小时前
国标GB28181视频平台EasyCVR如何搭建汽车修理厂远程视频网络监控方案
网络·音视频
无证驾驶梁嗖嗖8 小时前
FFMPEG大文件视频分割传输教程,微信不支持1G文件以上
音视频
小白教程10 小时前
Python爬取视频的架构方案,Python视频爬取入门教程
python·架构·音视频·python爬虫·python视频爬虫·python爬取视频教程
Json____12 小时前
springboot 处理编码的格式为opus的音频数据解决方案【java8】
spring boot·后端·音视频·pcm·音频处理·解码器·opus
赤鸢QAQ12 小时前
ffpyplayer+Qt,制作一个视频播放器
python·qt·音视频
EasyNTS12 小时前
ONVIF/RTSP/RTMP协议EasyCVR视频汇聚平台RTMP协议配置全攻略 | 直播推流实战教程
大数据·网络·人工智能·音视频
少年的云河月17 小时前
OpenHarmony 5.0版本视频硬件编解码适配
音视频·harmonyos·视频编解码·openharmony·codec hdi