当前位置: 首页 > article >正文

FFmpeg 自定义IO和格式转换

文章目录

  • 自定义IO
  • 音频重采样
  • 图像格式转换

自定义IO

通常解封装时,当调用avformat_open_input和avformat_find_stream_info时,FFmpeg内部会自动读取文件内容来查找信息。除此之外,我们也可以自定义IO,我们只要提供一个自定义的读/写函数提供给avformat_open_input函数作为回调函数,这样的话,当调用前面这两个函数时,FFmpeg内部通过回调函数来提供数据。

头文件包含新增
#include <libavformat/avio.h> // 输入输出
#include <libavutil/file.h> // 文件操作

struct BufferData
{
    uint8_t *buffer;    // 缓冲区
    size_t buffer_size; // 缓冲区大小
};

int read_packet(void *opaque, uint8_t *buf, int buf_size) // 读取数据包
{
    BufferData *buffer_data = (BufferData *)opaque; // 获取缓冲区数据结构
    if (buffer_data->buffer_size > 0)
    {
        int size = std::min(buf_size, (int)buffer_data->buffer_size); // 获取要读取的字节数
        memcpy(buf, buffer_data->buffer, size);                       // 读取数据
        buffer_data->buffer += size;                                  // 更新缓冲区指针
        buffer_data->buffer_size -= size;                             // 更新缓冲区大小
        return size;
    }
    return -1;
}

void Widget::customUI()
{
    AVFormatContext *fmt_ctx = nullptr;                  // 分配一个格式上下文
    AVIOContext *avio_ctx = nullptr;                     // 分配一个IO上下文
    uint8_t *buffer = nullptr;                           // 分配一个缓冲区
    uint8_t *avio_ctx_buffer = nullptr;                  // 分配一个IO上下文缓冲区
    size_t buffer_size = 0, avio_ctx_buffer_size = 4096; // 缓冲区大小

    const char *input_file = "../../source/audio.mp3"; // 输入文件名

    int ret = av_file_map(input_file, &buffer, &buffer_size, 0, NULL); // 打开输入文件,将文件内容映射到缓冲区
    if (ret < 0)
    {
        qDebug() << "打开输入文件失败";
        return;
    }
    BufferData buffer_data; // 创建缓冲区数据结构
    buffer_data.buffer = buffer;
    buffer_data.buffer_size = buffer_size;

    fmt_ctx = avformat_alloc_context(); // 分配一个格式上下文
    if (!fmt_ctx)
    {
        qDebug() << "分配格式上下文失败";
        return;
    }
    avio_ctx_buffer = (uint8_t *)av_malloc(avio_ctx_buffer_size);                                                         // 分配一个IO上下文缓冲区
    avio_ctx = avio_alloc_context(avio_ctx_buffer, avio_ctx_buffer_size, 0, &buffer_data, read_packet, nullptr, nullptr); // 分配一个IO上下文
    if (!avio_ctx)
    {
        qDebug() << "分配IO上下文失败";
        return;
    }
    fmt_ctx->pb = avio_ctx;                                            // 将IO上下文绑定到格式上下文
    ret = avformat_open_input(&fmt_ctx, input_file, nullptr, nullptr); // 打开输入文件
    if (ret < 0)
    {
        qDebug() << "打开输入文件失败";
        return;
    }
    avformat_find_stream_info(fmt_ctx, nullptr); // 查找流信息
    av_dump_format(fmt_ctx, 0, input_file, 0);   // 打印输入文件信息

    avio_context_free(&avio_ctx);       // 释放IO上下文
    avformat_close_input(&fmt_ctx);     // 关闭输入文件
    avformat_free_context(fmt_ctx);     // 释放格式上下文
    av_file_unmap(buffer, buffer_size); // 释放缓冲区
    qDebug() << "音频播放结束";
}


音频重采样

音频的播放是依赖于设备的,不同的设备它支持的音频格式不一样,要想在设备上播放就必须将音频格式转换成与设备对应的格式。音频在做编码的时候也是一样的,不同的音频编码器小不同的音频格式。

将一种音频格式转换成另一种音频格式,这个转换过程我们称之为重采样,重采样就是改变音频的采样率、采样格式、声道数参数。

一帧音频的数据量(字节) = c h a n n e l 数 × n b _ s a m p l e s 样本数 × 采样格式 一帧音频的数据量(字节)=channel数\times nb\_samples样本数 \times 采样格式 一帧音频的数据量(字节)=channel×nb_samples样本数×采样格式
AAC格式音频的样本数量一般是1024,MP3格式音频样本数量是1152。

一帧音频的播放时间 = 样本数量 ÷ 采样率 一帧音频的播放时间 = 样本数量 \div 采样率 一帧音频的播放时间=样本数量÷采样率

在做重采样时,重采样之前的播放时长和重采样后的播放时长是相等的。因此样本数量的计算,我们可以通过如下公式计算
源格式样本数 ÷ 源采样率 = 目标格式样本数量 ÷ 目标采样率 源格式样本数\div 源采样率 = 目标格式样本数量 \div 目标采样率 源格式样本数÷源采样率=目标格式样本数量÷目标采样率
如果出现小数,我们需要对其进行向上取整。

代码如下:
#include <libswresample/swresample.h> // 音频重采样头文件

void Widget::audioReSample()
{
    const char *input_file = "../../source/audio.aac";          // 输入文件名
    const char *output_file = "../../output/audioResample.pcm"; // 输出文件名

    AVFormatContext *fmt_ctx = nullptr;                                    // 分配一个格式上下文
    int ret = avformat_open_input(&fmt_ctx, input_file, nullptr, nullptr); // 打开输入文件
    if (ret < 0)
    {
        qDebug() << "打开输入文件失败";
        return;
    }
    avformat_find_stream_info(fmt_ctx, nullptr); // 查找流信息

    int audio_stream_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, nullptr, 0); // 查找音频流索引
    if (audio_stream_index < 0)
    {
        qDebug() << "找不到音频流";
        return;
    }
    const AVCodec *codec = avcodec_find_decoder(fmt_ctx->streams[audio_stream_index]->codecpar->codec_id); // 查找解码器
    AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);                                             // 分配一个解码器上下文
    avcodec_parameters_to_context(codec_ctx, fmt_ctx->streams[audio_stream_index]->codecpar);              // 将解码器参数复制到解码器上下文
    ret = avcodec_open2(codec_ctx, codec, nullptr);                                                        // 打开解码器
    if (ret < 0)
    {
        qDebug() << "打开解码器失败";
        return;
    }
    SwrContext *swr_ctx = nullptr; // 创建重采样上下文
    ret = swr_alloc_set_opts2(&swr_ctx, &codec_ctx->ch_layout, AV_SAMPLE_FMT_S16, 44100,
                              &codec_ctx->ch_layout, codec_ctx->sample_fmt, codec_ctx->sample_rate, 0, nullptr); // 设置重采样参数
    if (ret < 0)
    {
        qDebug() << "设置重采样参数失败";
        return;
    }
    ret = swr_init(swr_ctx); // 初始化重采样上下文
    if (ret < 0)
    {
        qDebug() << "初始化重采样上下文失败";
        return;
    }
    std::ofstream output_file_stream(output_file, std::ios::binary); // 打开输出文件
    if (!output_file_stream.is_open())
    {
        qDebug() << "打开输出文件失败";
        return;
    }
    AVPacket packet;                                                                                                                                          // 创建一个包
    AVFrame *frame = av_frame_alloc();                                                                                                                        // 创建一个帧
    uint8_t **out_buffer = nullptr;                                                                                                                           // 输出缓冲区
    int out_buffer_size = 0;                                                                                                                                  // 输出缓冲区大小
    int64_t out_Samples = av_rescale_rnd(swr_get_delay(swr_ctx, codec_ctx->sample_rate) + codec_ctx->frame_size, 44100, codec_ctx->sample_rate, AV_ROUND_UP); // 计算输出样本数
    av_samples_alloc_array_and_samples(&out_buffer, &out_buffer_size, 2, out_Samples, AV_SAMPLE_FMT_S16, 0);                                                  // 分配输出缓冲区
    while (av_read_frame(fmt_ctx, &packet) >= 0)                                                                                                              // 读取一个包
    {
        if (packet.stream_index == audio_stream_index) // 如果是音频包
        {
            ret = avcodec_send_packet(codec_ctx, &packet); // 发送一个包到解码器
            if (ret < 0)
            {
                return;
            }
            while (ret >= 0)
            {
                ret = avcodec_receive_frame(codec_ctx, frame); // 从解码器接收一个帧
                if (ret < 0)
                    break;
                swr_convert(swr_ctx, out_buffer, out_Samples, (const uint8_t **)frame->data, frame->nb_samples); // 重采样
                int out_buffer_size = av_samples_get_buffer_size(nullptr, 2, frame->nb_samples, AV_SAMPLE_FMT_S16, 1);
                if (out_buffer_size < 0)
                    return;
                output_file_stream.write((char *)out_buffer[0], out_buffer_size); // 写入输出文件
                av_frame_unref(frame);                                            // 释放帧
            }
        }
        av_packet_unref(&packet); // 释放包
    }
    ret = avcodec_send_packet(codec_ctx, nullptr); // 发送结束标志到解码器
    while (ret >= 0)
    {
        ret = avcodec_receive_frame(codec_ctx, frame); // 从解码器接收一个帧
        if (ret < 0)
        {
            break;
        }
        swr_convert(swr_ctx, out_buffer, out_Samples, (const uint8_t **)frame->data, frame->nb_samples); // 重采样
        int out_buffer_size = av_samples_get_buffer_size(nullptr, 2, frame->nb_samples, AV_SAMPLE_FMT_S16, 1);
        if (out_buffer_size < 0)
            return;
        output_file_stream.write((char *)out_buffer[0], out_buffer_size); // 写入输出文件
    }
    av_frame_free(&frame);            // 释放帧
    av_free(out_buffer[0]);           // 释放输出缓冲区
    output_file_stream.close();       // 关闭输出文件
    avformat_close_input(&fmt_ctx);   // 关闭输入文件
    avcodec_free_context(&codec_ctx); // 释放解码器上下文
    swr_free(&swr_ctx);               // 释放重采样上下文
    avformat_free_context(fmt_ctx);   // 释放格式上下文
    qDebug() << "音频转换完成";
}

图像格式转换

大多数的视频文件中保存的图像格式一般都是YUV格式,而这种格式的图像我们无法进行直接显示,需要先将其转成RGB格式才能够显示,如果我们还想要对图像进行放大或者缩小,那么FFmpeg的libswscale库给我们提供了这些功能。

头文件添加
#include <libswscale/swscale.h> // 视频重采样
#include <libavutil/imgutils.h> // 图像工具

void Widget::ImageReSample()
{
    const char *input_file = "../../source/video-30fps.mp4"; // 输入文件路径
    const char *output_file = "../../output/out.rgb";        // 输出文件路径

    AVFormatContext *fmt_ctx = nullptr;                                    // 格式上下文
    int ret = avformat_open_input(&fmt_ctx, input_file, nullptr, nullptr); // 打开输入文件
    if (ret < 0)
    {
        qDebug() << "无法打开输入文件";
        return;
    }
    ret = avformat_find_stream_info(fmt_ctx, nullptr); // 查找流信息
    if (ret < 0)
    {
        qDebug() << "无法查找流信息";
        return;
    }
    int video_stream_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0); // 查找视频流
    if (video_stream_index < 0)
    {
        qDebug() << "无法找到视频流";
        return;
    }
    const AVCodec *codec = avcodec_find_decoder(fmt_ctx->streams[video_stream_index]->codecpar->codec_id); // 查找解码器
    if (!codec)
    {
        qDebug() << "无法找到解码器";
        return;
    }
    AVCodecContext *codec_ctx = avcodec_alloc_context3(codec); // 分配解码器上下文
    if (!codec_ctx)
    {
        qDebug() << "无法分配解码器上下文";
        return;
    }
    ret = avcodec_parameters_to_context(codec_ctx, fmt_ctx->streams[video_stream_index]->codecpar); // 将流参数复制到解码器上下文
    if (ret < 0)
    {
        qDebug() << "无法复制流参数到解码器上下文";
        return;
    }
    ret = avcodec_open2(codec_ctx, codec, nullptr); // 打开解码器
    if (ret < 0)
    {
        qDebug() << "无法打开解码器";
        return;
    }
    SwsContext *sws_ctx = sws_getContext(codec_ctx->width, codec_ctx->height, codec_ctx->pix_fmt,
                                         640, 480, AV_PIX_FMT_RGB24, SWS_BICUBIC, nullptr, nullptr, nullptr); // 创建重采样上下文
    if (!sws_ctx)
    {
        qDebug() << "无法创建重采样上下文";
        return;
    }
    uint8_t *out_buffer[4];                                                                // 输出缓冲区
    int out_buffer_size;                                                                   // 输出缓冲区大小
    int lineSize[4];                                                                       // 行大小
    out_buffer_size = av_image_alloc(out_buffer, lineSize, 640, 480, AV_PIX_FMT_RGB24, 1); // 分配输出缓冲区
    if (out_buffer_size < 0)
    {
        qDebug() << "无法分配输出缓冲区";
        return;
    }
    std::ofstream output(output_file, std::ios::binary); // 打开输出文件
    if (!output.is_open())
    {
        qDebug() << "无法打开输出文件";
        return;
    }
    AVPacket *packet = av_packet_alloc();       // 分配AVPacket
    AVFrame *frame = av_frame_alloc();          // 分配AVFrame
    while (av_read_frame(fmt_ctx, packet) >= 0) // 读取帧
    {
        if (packet->stream_index == video_stream_index) // 如果是视频帧
        {
            ret = avcodec_send_packet(codec_ctx, packet); // 发送数据包到解码器
            if (ret < 0)
                break;
            while (ret >= 0)
            {
                ret = avcodec_receive_frame(codec_ctx, frame); // 接收解码后的帧
                if (ret < 0)
                    break;
                sws_scale(sws_ctx, (const uint8_t *const *)frame->data, frame->linesize, 0, frame->height, out_buffer, lineSize); // 重采样
                output.write((char *)out_buffer[0], out_buffer_size);                                                             // 写入文件
                av_frame_unref(frame);                                                                                            // 释放AVFrame
            }
        }
        av_packet_unref(packet); // 释放AVPacket
    }
    ret = avcodec_send_packet(codec_ctx, nullptr); // 发送空包结束解码
    while (ret >= 0)
    {
        ret = avcodec_receive_frame(codec_ctx, frame); // 接收解码后的帧
        if (ret < 0)
            break;
        sws_scale(sws_ctx, (const uint8_t *const *)frame->data, frame->linesize, 0, frame->height, out_buffer, lineSize); // 重采样
        output.write((char *)out_buffer[0], out_buffer_size);                                                             // 写入文件
        av_frame_unref(frame);                                                                                            // 释放AVFrame
    }
    av_packet_free(&packet);          // 释放AVPacket
    av_frame_free(&frame);            // 释放AVFrame
    av_free(out_buffer);              // 释放输出缓冲区
    output.close();                   // 关闭输出文件
    avcodec_free_context(&codec_ctx); // 释放解码器上下文
    avformat_close_input(&fmt_ctx);   // 关闭输入文件
    avformat_free_context(fmt_ctx);   // 释放格式上下文
    qDebug() << "视频图片转换完成";
}

在这里插入图片描述


http://www.kler.cn/a/523859.html

相关文章:

  • YOLOv11-ultralytics-8.3.67部分代码阅读笔记-head.py
  • games101-作业2
  • ubuntu取消输入密码
  • 基于PyQt设计的智能停车管理系统
  • Ceph:关于Ceph 中 RADOS 块设备快照克隆管理的一些笔记整理(12)
  • 区块链在能源行业的应用场景
  • < OS 有关 > Android 手机 SSH 客户端 app: connectBot
  • JavaScript正则表达式
  • 【04-自己画P封装,并添加已有3D封装】
  • Ansible自动化运维实战--script、unarchive和shell模块(6/8)
  • 【第九天】零基础入门刷题Python-算法篇-数据结构与算法的介绍-六种常见的图论算法(持续更新)
  • leetcode 1493. 删掉一个元素以后全为 1 的最长子数组
  • 书生大模型实战营3
  • vs2013 使用 eigen 库编译时报 C2059 错的解决方法
  • 大数据Hadoop入门3
  • 2023年吉林省职业院校技能大赛网络系统管理样题-网络配置(华三代码)
  • electron typescript运行并设置eslint检测
  • (学习总结21)C++11 异常与智能指针
  • 第24章 质量培训与探啥未来
  • deepseek-r1 本地部署
  • 【SH】Windows禁用Alt+F4关机、重启、注销等功能,只保留关闭应用的功能
  • 利用 PyTorch 动态计算图和自动求导机制实现自适应神经网络
  • 炒股-技术面分析(技术指标)
  • JJJ:linux时间子系统相关术语
  • 【MySQL-7】事务
  • 【WebGL】纹理