文章目录

  • 1、总流程
  • 2、解析流程
  • 3、解码流程
  • 4、完整代码

1、总流程

  • 创建解析器、解码器、AVPacket和AVFrame
  • 打开文件,将mp3数据读入缓冲区
  • 解析mp3数据(在 main 函数中完成)
  • 解码,并将解码后的pcm数据写入文件(在 my_audio_decode 函数中完成

2、解析流程

mp3文件可能比较大,一次性读取会浪费比较多的内存,采用边读边解析办法。
如下图所示,红色表示buf缓冲区,首先从mp3文件里读取数据填满buf,然后开始解析,buf_index负责标记解析结束位置,buf_size负责记录buf里还剩多少数据未解析,如果buf_size小于4096,则将剩余未解析的数据移动到buf起始位置,然后再次从文件里读数据填满buf,同时buf_index也移动到buf起始位置,以此循环直到文件读取结束。

  • 代码实现如下
    buf_size = fread(buf, 1, AUDIO_BUF_SIZE, fl_in); // 读mp3文件while (buf_size > 0){ret = av_parser_parse2(parser, ctx, &pkt->data, &pkt->size, buf_index, buf_size,AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);if (ret < 0) { exit(1); }  // parser失败,直接退出程序buf_index += ret;          // 跳过已经解析的数据buf_size  -= ret;          // 缓冲区还未解析的mp3数据大小if (pkt->size)my_audio_decode(ctx, pkt, frame, fl_out); // 解码并将解码后的pcm数据写入文件if (buf_size < 4096)  // 缓冲区未解析的音频数据小于4096,再从mp3文件中读取一点数据放入缓冲区{memmove(buf, buf_index, buf_size); // 把还未解析的数据移动到buf的起始位置buf_index = buf;                   // 当前解析结束位置也移动到buf起始位置len = fread(buf_index + buf_size, 1, AUDIO_BUF_SIZE - buf_size, fl_in);if (len > 0) buf_size += len;}}

3、解码流程

解码流程相对简单,这里单独用一个函数封装。解析完后直接从 AVPacket 里面将数据解码得到解码数据即可,然后写入文件。音频文件和视频文件的写法不同,注意做区分,代码实现如下:

// 解码
static void my_decode(AVCodecContext *ctx, AVPacket *pkt, AVFrame *frame, FILE *fl_out, AVCodecID codec_id)
{int size;int ret = avcodec_send_packet(ctx, pkt);  // 发送packet到解码线程,不占用CPU资源if (ret < 0) { return; }                  // 需要注意 AVERROR(EAGAIN) 错误处理while (ret >= 0){ret = avcodec_receive_frame(ctx, frame);         //从解码线程中获取解码接口,不占用CPU资源if (ret != 0) break;size = av_get_bytes_per_sample(ctx->sample_fmt); // 获取每个样本的字节数if (size < 0) { exit(1); }                       // 异常!大小计算失败// 将解码后的yuv或pcm数据写入文件if (codec_id == AV_CODEC_ID_H264) // h264视频{print_video_format(frame); // 打印视频基本参数for(int j=0; j<frame->height; j++)   // Yfwrite(frame->data[0] + j * frame->linesize[0], 1, frame->width, fl_out);for(int j=0; j<frame->height/2; j++) // Ufwrite(frame->data[1] + j * frame->linesize[1], 1, frame->width/2, fl_out);for(int j=0; j<frame->height/2; j++) // Vfwrite(frame->data[2] + j * frame->linesize[2], 1, frame->width/2, fl_out);}else // mp3或aac音频{print_audio_format(frame); // 打印音频基本参数for (int i = 0; i < frame->nb_samples; i++)  // 采样率{for (int channel = 0; channel < ctx->channels; ++channel)   // 声道数{fwrite(frame->data[channel] + size*i, 1, size, fl_out); // 将pcm数据写入文件}}}}
}

4、完整代码

以下代码再Qt5.14.0中编译测试OK,运行目录需要放置一个believe.mp3文件,程序执行完成后会在运行目录下生成一个believe.pcm文件,使用以下命令即可测试转换是否成功:
音频:ffplay -ar 48000 -ac 2 -f f32le believe.pcm
视频:ffplay -pixel_format yuv420p -video_size 766x322 -framerate 25 out.yuv


#ifdef __cplusplus
extern "C"
{#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libavutil/frame.h>
#include <libavutil/mem.h>
#include <libavcodec/avcodec.h>
#ifdef __cplusplus
}
#endif// 缓冲区大小
#define BUF_SIZE    20480// 解码
static void my_decode(AVCodecContext *ctx, AVPacket *pkt, AVFrame *frame, FILE *fl_out, AVCodecID codec_id)
{int size;int ret = avcodec_send_packet(ctx, pkt);  // 发送packet到解码线程,不占用CPU资源if (ret < 0) { return; }                  // 需要注意 AVERROR(EAGAIN) 错误处理while (ret >= 0){ret = avcodec_receive_frame(ctx, frame);         //从解码线程中获取解码接口,不占用CPU资源if (ret != 0) break;size = av_get_bytes_per_sample(ctx->sample_fmt); // 获取每个样本的字节数if (size < 0) { exit(1); }                       // 异常!大小计算失败// 将解码后的yuv或pcm数据写入文件if (codec_id == AV_CODEC_ID_H264) // h264视频{for(int j=0; j<frame->height; j++)   // Yfwrite(frame->data[0] + j * frame->linesize[0], 1, frame->width, fl_out);for(int j=0; j<frame->height/2; j++) // Ufwrite(frame->data[1] + j * frame->linesize[1], 1, frame->width/2, fl_out);for(int j=0; j<frame->height/2; j++) // Vfwrite(frame->data[2] + j * frame->linesize[2], 1, frame->width/2, fl_out);}else // mp3或aac音频{for (int i = 0; i < frame->nb_samples; i++)  // 采样率{for (int channel = 0; channel < ctx->channels; ++channel)   // 声道数{fwrite(frame->data[channel] + size*i, 1, size, fl_out); // 将pcm数据写入文件}}}}
}// YUV视频播放范例:ffplay -pixel_format yuv420p -video_size 766x322 -framerate 25 out.yuv
// pcm音频播放示例:ffplay -ar 48000 -ac 2 -f f32le believe.pcm
int main()
{const char           *outfilename    = "out.pcm";         // 要输出的yuv文件路径const char           *infilename     = "believe.mp3";     // h264文件路径,200kbps 766x322 10sFILE                 *fl_in          = NULL;FILE                 *fl_out         = NULL;const AVCodec        *codec          = NULL;              // 解码器AVCodecContext       *ctx            = NULL;              // 解码器上下文AVCodecParserContext *parser         = NULL;              // 解析器,解码前需要先解析AVPacket             *pkt            = av_packet_alloc(); // 接封装后的帧AVFrame              *frame          = av_frame_alloc();  // 解码后的帧uint8_t              buf[BUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];    // yuv帧数据缓冲区size_t               buf_size        = 0;                 // 缓冲区未解析的数据大小uint8_t              *buf_index      = buf;               // 当前解析位置,初始化为buf起始位置enum AVCodecID       codec_id        = AV_CODEC_ID_MP3;   // 解码器IDint                  len             = 0;                 // 从文件中读到的数据大小int                  ret             = 0;// ===== 根据文件名后缀判断是视频还是音频 =====if (strstr(infilename, "aac") != NULL){             // aac音频裸流codec_id = AV_CODEC_ID_AAC;} else if (strstr(infilename, "mp3") != NULL) {     // mp3音频裸流codec_id = AV_CODEC_ID_MP3;} else if (strstr(infilename, "h264") != NULL) {    // h264视频裸流codec_id = AV_CODEC_ID_H264;} else {return 1; // 不支持的格式}// ================== 1、初始化 ==================if (!frame || !pkt){ exit(1); }         // 帧空间分配失败codec = avcodec_find_decoder(codec_id); // 查找解码器if (!codec)     { exit(1); }parser = av_parser_init(codec->id);     // 获取裸流的解析器if (!parser)    { exit(1); }ctx = avcodec_alloc_context3(codec);    // 分配codec上下文if (!ctx)       { exit(1); }ret = avcodec_open2(ctx, codec, NULL);  // 将解码器和解码器上下文进行关联if (ret < 0)    { exit(1); }fl_in  = fopen(infilename, "rb");       // 打开输入文件fl_out = fopen(outfilename, "wb");      // 打开输出文件if (!fl_in || !fl_out) {av_free(ctx);exit(1);}// ================== 2、开始解码 ==================buf_size = fread(buf, 1, BUF_SIZE, fl_in); // 读mp3文件while (buf_size > 0){ret = av_parser_parse2(parser, ctx, &pkt->data, &pkt->size, buf_index, buf_size,AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);if (ret < 0) { exit(1); }  // parser失败,直接退出程序buf_index += ret;          // 跳过已经解析的数据buf_size  -= ret;          // 缓冲区还未解析的mp3数据大小if (pkt->size)my_decode(ctx, pkt, frame, fl_out, codec_id); // 解码并将解码后的pcm数据写入文件if (buf_size < 4096)  // 缓冲区音频数据小于4096,再从mp3文件中读取一点数据放入缓冲区{memmove(buf, buf_index, buf_size); // 把还未解析的数据移动到buf的起始位置buf_index = buf;                   // 当前解析位置也移动到buf起始位置len = fread(buf_index + buf_size, 1, BUF_SIZE - buf_size, fl_in);if (len > 0) buf_size += len;}}// ================== 3、解码结束 ==================pkt->data = NULL; // 进入drain modepkt->size = 0;my_decode(ctx, pkt, frame, fl_out, codec_id);fclose(fl_out);   // 关闭文件fclose(fl_in);// 释放空间avcodec_free_context(&ctx);av_parser_close(parser);av_frame_free(&frame);av_packet_free(&pkt);printf("\n转换成功\n");return 0;
}

说明:代码中通过文件后缀名判断文件是音频还是视频,这种方式不太可取,后面再考虑优化一下。

FFmpeg系列(二)—— 音视频裸流转换:mp3转pcm、h264转YUV相关推荐

  1. 基于FFmpeg H264 + G711A 音视频裸流合并 MP4文件 ( G711A 转 AAC)

    https://blog.csdn.net/haiyangyunbao813/article/details/101788264

  2. Android FFmpeg系列——5 音视频同步播放

    https://blog.csdn.net/JohanMan/article/details/83176144

  3. FFmpeg学习(音视频理论知识)

    文章目录 1. 音视频理论知识 1.1 基本概念 1.1.1 音视频必备的基本概念 常用的视频封装格式 常用的视频编码器 常用的音频编程器: 视频流 裸数据YUV 1.1.2 音视频常见处理 采集 处 ...

  4. 使用FFmpeg命令处理音视频

    文章目录 前言 一.ffprobe相关命令 1.使用ffprobe查看音频文件的信息 2.使用ffprobe查看视频文件的信息 二.ffplay相关命令 1.基本的ffplay命令 2.音视频同步命令 ...

  5. ffmpeg系列之mp4与ts格式转换

    ffmpeg系列之mp4与ts格式转换 1. M3U 协议解析 1.1.Tag说明 2.HLS 与 M3U8 3.通过ffmpeg进行转换 3.1. mp4格式转换为ts格式 3.2. ts格式拼接命 ...

  6. linux下ts转mp4,ffmpeg安装及实现视频格式的转换、分片(ts m3u8)

    1.简介 FFmpeg是一套可以用来记录.转换数字音频.视频,并能将其转化为流的开源计算机程序.采用LGPL或GPL许可证.它提供了录制.转换以及流化音视频的完整解决方案 2.相关 系统版本:Cent ...

  7. FFmpeg 工具:音视频开发都用它,快@你兄弟来看丨音视频工具

    (本文基本逻辑:ffmpeg 常用命令介绍 → ffplay 常用命令介绍 → ffprobe 常用命令介绍) 从事音视频开发的程序员几乎都应该知道或使用过 FFmpeg.FFmpeg 是一个开源软件 ...

  8. 基于FFmpeg 实现RTSP, 音视频编解码,视频流添加文字,音视频合成MP4

    前言: 最近闲这没事,整理了一下之前开发过的音视频编解码库,主要基于ffmpeg,实现音视频的编解码.视频流添加文字,音视频同步到MP4等功能.有需要的小伙伴可以参考参考,如果写的有什么不对的地方,欢 ...

  9. android音视频工程师,音视频学习 (十三) Android 中通过 FFmpeg 命令对音视频编辑处理(已开源)...

    ## 音视频学习 (十三) Android 中通过 FFmpeg 命令对音视频编辑处理(已开源) ## 视音频编辑器 ## 前言 有时候我们想对音视频进行加工处理,比如视频编辑.添加字幕.裁剪等功能处 ...

最新文章

  1. centos 安装git_开源物联网MQTT 5.0服务器——EMQ安装及运行教程
  2. python1000个常用代码-1000个常用的Python库和示例代码
  3. java 基本数据类型及自己主动类型提升
  4. Mysql快照读和当前读
  5. python提取日志内容_Python正则提取日志内容
  6. 想要你的HTTP稳定不蹦,必须吃透「负载均衡」
  7. 每一次结束只是一次新的起点,深有体会。
  8. 1731: [Usaco2005 dec]Layout 排队布局*
  9. Linux 基础 入门
  10. 龙芯3A3000上实现BLFS的轻量级桌面LXDE
  11. qpython3手机版怎么运行不了_QPython3手机版
  12. linux 卸载oracle库,Linux下完美卸载 Oracle
  13. 依靠语言和依靠图书馆
  14. IDEA中文切换回英文
  15. 『杭电1888』Rectangular Polygons
  16. (已解决)win环境下 maven 报错:致命错误: 在类路径或引导类路径中找不到程序包 java.lang
  17. Excel按列合并相同相邻单元格和拆分单元格
  18. JS的特性:异步 + 事件驱动
  19. 笔记本使用wifi链接外网,同时有线链接内网详细设置,最后有bat文件,方便随时切换
  20. 【NA】Householder变换

热门文章

  1. matlab沪深a股量化投资培训班,MATLAB沪深A股量化投资培训
  2. 手把手教你搭建网站(零基础,不用写代码)
  3. FPGA PLL时钟经 ODDR送到管脚
  4. G711 G723 G729,带宽计算
  5. 相机分辨率越高,成像效果就一定越好嘛
  6. 多少达芬奇发明实际起作用
  7. RegionServer 宕机恢复流程
  8. 计算机二级一做题就不会,2017年计算机二级考试做题经验分享
  9. jdbcService层
  10. 湖广填四川与安岳姓氏源流