解码mp4文件分别存储为pcm,yuv文件

简介: 使用FFmpeg库在C++中解码MP4文件,并将音频数据存储为PCM格式,视频数据存储为YUV格式。
// 解码分别写入对应文件
#include "myLog.h"
#include <iostream>

extern "C"
{
#include <libavformat\avformat.h>
#include <libavutil\avutil.h>
#include <libavcodec\avcodec.h>
#include <libavutil\imgutils.h>
#include <libavutil\samplefmt.h>
}
#pragma comment(lib, "avformat.lib")
#pragma comment(lib, "avcodec.lib")

class Decoder
{
public:
    Decoder(std::string src_filename, std::string dst_audio_filename, std::string dst_video_filename);
    ~Decoder();

    // 解封装
    int demuxer();

    // 解码
    int decodec();

    // 打印音视频播放格式
    void print_audio_play_str();
private:
    // 打开解码器
    int open_decodec();

    // 解码pkt
    int decode_pkt(AVCodecContext* codec_ctx, AVPacket* pkt);

    // 输出视频帧
    int output_video_frame(AVFrame* frame);

    // 输出音频帧
    int output_audio_frame(AVFrame* frame);

    // 获取音频格式
    int get_format_from_sample_fmt(std::string& fmtStr, enum AVSampleFormat sample_fmt);
private:
    AVFormatContext* ifmt_ctx_;                    // 格式上下文
    AVCodecContext*  video_decode_ctx_;            // 视频解码器上下文
    AVCodecContext*  audio_decode_ctx_;            // 音频解码器上下文

    int video_idx_;                                // 视频索引                        
    AVStream* video_stream_;                    // 视频流

    int audio_idx_;                                // 音频索引
    AVStream* audio_stream_;                    // 音频流

    std::string src_filename_;                    // 输入文件路径
    std::string dst_audio_filename_;            // 输出音频pcm文件路径
    std::string dst_video_filename_;            // 输出视频yuv路径                            
    FILE* dst_video_fp_;
    FILE* dst_audio_fp_;

    AVFrame* frame_;
    uint8_t* video_dst_data_[4];                // 记录video一行数据
    int video_dst_buf_size_;                    // 分配的真实buf大小 
    int linesize_[4];                            // 每行数据大小(YUV RGB 第四个为保留字段)
};

Decoder::Decoder(std::string src_filename, std::string dst_audio_filename, std::string dst_video_filename)
: ifmt_ctx_(nullptr)
, video_decode_ctx_(nullptr)
, audio_decode_ctx_(nullptr)
, video_stream_(nullptr)
, audio_stream_(nullptr)
, dst_video_fp_(nullptr)
, dst_audio_fp_(nullptr)
, frame_(nullptr)
, src_filename_(src_filename)
, dst_audio_filename_(dst_audio_filename)
, dst_video_filename_(dst_video_filename)
{
    for (int i = 0; i < 4; i++)
    {
        video_dst_data_[i] = NULL;
    }
}

Decoder::~Decoder()
{
    // 释放相关资源
    if (ifmt_ctx_)
    {
        avformat_close_input(&ifmt_ctx_);
    }
    if (audio_decode_ctx_)
    {
        avcodec_free_context(&audio_decode_ctx_);
    }
    if (video_decode_ctx_)
    {
        avcodec_free_context(&video_decode_ctx_);
    }
    if (dst_audio_fp_)
    {
        fclose(dst_audio_fp_);
    }    
    if (dst_video_fp_)
    {
        fclose(dst_video_fp_);
    }
    if (frame_)
    {
        av_frame_free(&frame_);
    }
    if (video_dst_data_)
    {
        av_free(video_dst_data_[0]);    // 释放数据指针(其它三个指针无需释放) data[0]指向缓冲区开始位置,data[1] - data[3]也是指向data[0]的数据指针
    }
}

int Decoder::demuxer()
{
    // 1. 打开文件
    int nRet = avformat_open_input(&ifmt_ctx_, src_filename_.c_str(), NULL, NULL);
    if (nRet < 0)
    {
        LOG_WARNING("avformat_open_input error\n");
        return -1;
    }

    // 2. 查找流信息
    nRet = avformat_find_stream_info(ifmt_ctx_, NULL);
    if (nRet < 0)
    {
        avformat_close_input(&ifmt_ctx_);
        LOG_WARNING("avformat_find_stream_info error\n");
        return -2;
    }

    // 3. 获取音视频流索引
    audio_idx_ = av_find_best_stream(ifmt_ctx_, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
    video_idx_ = av_find_best_stream(ifmt_ctx_, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);

    return 0;
}

int Decoder::open_decodec()
{
    // 打开音频解码器
    if (audio_idx_ >= 0)
    {
        audio_stream_ = ifmt_ctx_->streams[audio_idx_];
        // 查找解码器
        AVCodec* audio_decodec = avcodec_find_decoder(audio_stream_->codecpar->codec_id);
        if (!audio_decodec)
        {
            LOG_WARNING("avcodec_find_decoder error\n");
            return AVERROR(ENOMEM);
        }

        // 给解码上下文分配内存
        audio_decode_ctx_ = avcodec_alloc_context3(audio_decodec);
        if (!audio_decode_ctx_)
        {
            LOG_WARNING("avcodec_alloc_context3 error\n");
            return AVERROR(ENOMEM);
        }

        // 拷贝解码参数到解码上下文
        int nRet = avcodec_parameters_to_context(audio_decode_ctx_, audio_stream_->codecpar);
        if (nRet < 0)
        {
            LOG_WARNING("avcodec_parameters_to_context error\n");
            return -1;
        }
        // 打开解码器
        nRet = avcodec_open2(audio_decode_ctx_, audio_decodec, NULL);
        if (nRet < 0)
        {
            LOG_WARNING("avcodec_open2 error\n");
            return -2;
        }

        dst_audio_fp_ = fopen(dst_audio_filename_.c_str(), "wb");
        if (!dst_audio_fp_)
        {
            LOG_WARNING("fopen dst_audio_filename_ error\n");
            return -1;
        }
    }

    // 打开视频解码器
    if (video_idx_ >= 0)
    {
        video_stream_ = ifmt_ctx_->streams[video_idx_];
        // 查找解码器
        AVCodec* video_decodec = avcodec_find_decoder(video_stream_->codecpar->codec_id);
        if (!video_decodec)
        {
            LOG_WARNING("avcodec_find_decoder error\n");
            return AVERROR(ENOMEM);
        }

        // 给解码上下文分配内存
        video_decode_ctx_ = avcodec_alloc_context3(video_decodec);
        if (!video_decode_ctx_)
        {
            LOG_WARNING("avcodec_alloc_context3 error\n");
            return AVERROR(ENOMEM);
        }

        // 拷贝解码参数到解码上下文
        int nRet = avcodec_parameters_to_context(video_decode_ctx_, video_stream_->codecpar);
        if (nRet < 0)
        {
            LOG_WARNING("avcodec_parameters_to_context error\n");
            return -1;
        }
        // 打开解码器
        nRet = avcodec_open2(video_decode_ctx_,video_decodec, NULL);
        if (nRet < 0)
        {
            LOG_WARNING("avcodec_open2 error\n");
            return -2;
        }

        video_dst_buf_size_ = av_image_alloc(video_dst_data_, linesize_,
            video_decode_ctx_->width, video_decode_ctx_->height, video_decode_ctx_->pix_fmt, 1);

        dst_video_fp_ = fopen(dst_video_filename_.c_str(), "wb");
        if (!dst_video_fp_)
        {
            LOG_WARNING("fopen dst_video_filename_ error\n");
            return -1;
        }
    }
}

int Decoder::decodec()
{
    // 打开解码器
    int nRet = open_decodec();
    if (nRet < 0)
    {
        LOG_WARNING("open_decodec error\n");
        return -666;
    }

    AVPacket* pkt = av_packet_alloc();
    av_init_packet(pkt);
    pkt->data = NULL;
    pkt->size = 0;

    while (av_read_frame(ifmt_ctx_, pkt) >= 0)
    {
        if (pkt->stream_index == audio_idx_)    // 音频包
        {
            nRet = decode_pkt(audio_decode_ctx_, pkt);
        }
        else if (pkt->stream_index == video_idx_) // 视频包
        {
            nRet = decode_pkt(video_decode_ctx_, pkt);
        }
        av_packet_unref(pkt);
        if (nRet < 0)
        {
            break;
        }
    }

    // 冲刷解码器
    if (video_decode_ctx_)
    {
        decode_pkt(video_decode_ctx_, NULL);
    }
    if (audio_decode_ctx_)
    {
        decode_pkt(video_decode_ctx_, NULL);
    }
    return 0;
}

int Decoder::decode_pkt(AVCodecContext* codec_ctx, AVPacket* pkt)
{
    int nRet = avcodec_send_packet(codec_ctx, pkt);
    if (nRet < 0)
    {
        LOG_WARNING("avcodec_send_packet fflush codec\n");
        return -1;
    }

    // 一个包可能对应多个frame
    frame_ = av_frame_alloc();
    while (nRet >= 0)
    {
        nRet = avcodec_receive_frame(codec_ctx, frame_);
        if (nRet < 0)
        {
            if (nRet == AVERROR_EOF || nRet == AVERROR(EAGAIN))
                return 0;
            return nRet;
        }
        // 写数据
        if (codec_ctx->codec->type == AVMEDIA_TYPE_AUDIO)
        {
            nRet = output_audio_frame(frame_);
        }
        else if (codec_ctx->codec->type == AVMEDIA_TYPE_VIDEO)
        {
            nRet = output_video_frame(frame_);
        }
        av_frame_unref(frame_);
        if (nRet < 0)
            return nRet;
    }
}

int Decoder::output_video_frame(AVFrame* frame)
{
    if (video_dst_buf_size_ < 0)
    {
        LOG_WARNING("av_image_alloc error\n");
        return -2;
    }
    // 写入帧到文件
    if (frame->width != video_decode_ctx_->width ||
        frame->height != video_decode_ctx_->height ||
        frame->format != video_decode_ctx_->pix_fmt)
    {
        LOG_WARNING("frame and video_decode_ctx_ error\n");
        return -3;
    }
    // 填充数据到 video_dst_data
    av_image_copy(video_dst_data_, linesize_,
        (const uint8_t**)(frame->data), frame->linesize,
        video_decode_ctx_->pix_fmt, video_decode_ctx_->width, video_decode_ctx_->height);
    size_t write_video_size = fwrite(video_dst_data_[0], 1, video_dst_buf_size_, dst_video_fp_);
    fflush(dst_video_fp_);
    return 0;
}

int Decoder::output_audio_frame(AVFrame* frame)
{
    // 计算一帧音频数据的bytes
    size_t a_sample_point_bytes = frame->nb_samples * av_get_bytes_per_sample((AVSampleFormat)frame->format);
    // 写入数据到文件
    fwrite(frame->extended_data[0], 1, a_sample_point_bytes, dst_audio_fp_);
    fflush(dst_audio_fp_);
    return 0;
}

int Decoder::get_format_from_sample_fmt(std::string& fmtStr, enum AVSampleFormat sample_fmt)
{
    struct sample_fmt_entry {
        enum AVSampleFormat sample_fmt; 
        std::string fmt_be;
        std::string fmt_le;
    } sample_fmt_entries[] = {
        { AV_SAMPLE_FMT_U8, "u8", "u8" },
        { AV_SAMPLE_FMT_S16, "s16be", "s16le" },
        { AV_SAMPLE_FMT_S32, "s32be", "s32le" },
        { AV_SAMPLE_FMT_FLT, "f32be", "f32le" },
        { AV_SAMPLE_FMT_DBL, "f64be", "f64le" },
    };

    for (int i = 0; i < FF_ARRAY_ELEMS(sample_fmt_entries); i++) {
        struct sample_fmt_entry *entry = &sample_fmt_entries[i];
        if (sample_fmt == entry->sample_fmt) {
            fmtStr = AV_NE(entry->fmt_be, entry->fmt_le);
            return 0;
        }
    }

    fprintf(stderr, "sample format %s is not supported as output format\n", av_get_sample_fmt_name(sample_fmt));
    return -1;
}

void Decoder::print_audio_play_str()
{
    if (audio_stream_) {
        enum AVSampleFormat sfmt = audio_decode_ctx_->sample_fmt;
        int n_channels = audio_decode_ctx_->channels;
        std::string fmtStr;

        if (av_sample_fmt_is_planar(sfmt))    // 是planar格式
        {
            const char *packed = av_get_sample_fmt_name(sfmt);
            LOG_INFO("Warning: the sample format the decoder produced is planar "
                "(%s). This example will output the first channel only.\n",
                packed ? packed : "?");
            sfmt = av_get_packed_sample_fmt(sfmt);
            n_channels = 1;
        }

        int ret = -1;
        if ((ret = get_format_from_sample_fmt(fmtStr, sfmt)) < 0)
            return;

        LOG_INFO("Play the output audio file with the command:\n"
            "ffplay -f %s -ac %d -ar %d %s\n",
            fmtStr.c_str(), n_channels, audio_decode_ctx_->sample_rate,
            dst_audio_filename_.c_str());

        LOG_INFO("play video yuv:\nffplay -video_size %dx%d -f rawvideo -pixel_format yuv420p %s",
            video_decode_ctx_->width, video_decode_ctx_->height, dst_video_filename_.c_str());
    }
}

int main()
{
    std::string src_filename = "./MediaFile/HTA_13s.mp4";    // input_5s
    std::string dst_audio_filename = "./MediaFile/HTA_13s_decodec_audio.pcm"; // HTA_13s_decodec_audio
    std::string dst_video_filename = "./MediaFile/HTA_13s_decodec_video.yuv"; // HTA_13s_decodec_video

    Decoder decoder(src_filename, dst_audio_filename, dst_video_filename);
    // 解复用
    int nRet = decoder.demuxer();
    if (nRet < 0)
    {
        LOG_WARNING("main demuxer error\n");
        return -666;
    }
    // 解码写入到文件
    nRet = decoder.decodec();
    if (nRet < 0)
    {
        LOG_WARNING("main decodec error\n");
        return -777;
    }

    LOG_INFO("decode finish, write file success\n");
    decoder.print_audio_play_str();

    getchar();
    return 0;
}

注意:使用av_image_alloc为一个char* data[4]的数组分配内存时,其内部只是为data[0]分配内存,其余的data[1] - data[3]指针指向data[0]的某个位置(根据像素格式的不同)

相关文章
|
27天前
|
弹性计算 人工智能 架构师
阿里云携手Altair共拓云上工业仿真新机遇
2024年9月12日,「2024 Altair 技术大会杭州站」成功召开,阿里云弹性计算产品运营与生态负责人何川,与Altair中国技术总监赵阳在会上联合发布了最新的“云上CAE一体机”。
阿里云携手Altair共拓云上工业仿真新机遇
|
4天前
|
人工智能 Rust Java
10月更文挑战赛火热启动,坚持热爱坚持创作!
开发者社区10月更文挑战,寻找热爱技术内容创作的你,欢迎来创作!
438 17
|
7天前
|
JSON 自然语言处理 数据管理
阿里云百炼产品月刊【2024年9月】
阿里云百炼产品月刊【2024年9月】,涵盖本月产品和功能发布、活动,应用实践等内容,帮助您快速了解阿里云百炼产品的最新动态。
阿里云百炼产品月刊【2024年9月】
|
20天前
|
存储 关系型数据库 分布式数据库
GraphRAG:基于PolarDB+通义千问+LangChain的知识图谱+大模型最佳实践
本文介绍了如何使用PolarDB、通义千问和LangChain搭建GraphRAG系统,结合知识图谱和向量检索提升问答质量。通过实例展示了单独使用向量检索和图检索的局限性,并通过图+向量联合搜索增强了问答准确性。PolarDB支持AGE图引擎和pgvector插件,实现图数据和向量数据的统一存储与检索,提升了RAG系统的性能和效果。
|
7天前
|
Linux 虚拟化 开发者
一键将CentOs的yum源更换为国内阿里yum源
一键将CentOs的yum源更换为国内阿里yum源
379 2
|
22天前
|
人工智能 IDE 程序员
期盼已久!通义灵码 AI 程序员开启邀测,全流程开发仅用几分钟
在云栖大会上,阿里云云原生应用平台负责人丁宇宣布,「通义灵码」完成全面升级,并正式发布 AI 程序员。
|
24天前
|
机器学习/深度学习 算法 大数据
【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析
2024“华为杯”数学建模竞赛,对ABCDEF每个题进行详细的分析,涵盖风电场功率优化、WLAN网络吞吐量、磁性元件损耗建模、地理环境问题、高速公路应急车道启用和X射线脉冲星建模等多领域问题,解析了问题类型、专业和技能的需要。
2600 22
【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析
|
6天前
|
存储 人工智能 搜索推荐
数据治理,是时候打破刻板印象了
瓴羊智能数据建设与治理产品Datapin全面升级,可演进扩展的数据架构体系为企业数据治理预留发展空间,推出敏捷版用以解决企业数据量不大但需构建数据的场景问题,基于大模型打造的DataAgent更是为企业用好数据资产提供了便利。
286 2
|
4天前
|
编译器 C#
C#多态概述:通过继承实现的不同对象调用相同的方法,表现出不同的行为
C#多态概述:通过继承实现的不同对象调用相同的方法,表现出不同的行为
106 65
|
24天前
|
机器学习/深度学习 算法 数据可视化
【BetterBench博士】2024年中国研究生数学建模竞赛 C题:数据驱动下磁性元件的磁芯损耗建模 问题分析、数学模型、python 代码
2024年中国研究生数学建模竞赛C题聚焦磁性元件磁芯损耗建模。题目背景介绍了电能变换技术的发展与应用,强调磁性元件在功率变换器中的重要性。磁芯损耗受多种因素影响,现有模型难以精确预测。题目要求通过数据分析建立高精度磁芯损耗模型。具体任务包括励磁波形分类、修正斯坦麦茨方程、分析影响因素、构建预测模型及优化设计条件。涉及数据预处理、特征提取、机器学习及优化算法等技术。适合电气、材料、计算机等多个专业学生参与。
1582 17
【BetterBench博士】2024年中国研究生数学建模竞赛 C题:数据驱动下磁性元件的磁芯损耗建模 问题分析、数学模型、python 代码