FFmpeg系列(二)—— 音视频裸流转换:mp3转pcm、h264转YUV
文章目录
- 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相关推荐
- 基于FFmpeg H264 + G711A 音视频裸流合并 MP4文件 ( G711A 转 AAC)
https://blog.csdn.net/haiyangyunbao813/article/details/101788264
- Android FFmpeg系列——5 音视频同步播放
https://blog.csdn.net/JohanMan/article/details/83176144
- FFmpeg学习(音视频理论知识)
文章目录 1. 音视频理论知识 1.1 基本概念 1.1.1 音视频必备的基本概念 常用的视频封装格式 常用的视频编码器 常用的音频编程器: 视频流 裸数据YUV 1.1.2 音视频常见处理 采集 处 ...
- 使用FFmpeg命令处理音视频
文章目录 前言 一.ffprobe相关命令 1.使用ffprobe查看音频文件的信息 2.使用ffprobe查看视频文件的信息 二.ffplay相关命令 1.基本的ffplay命令 2.音视频同步命令 ...
- ffmpeg系列之mp4与ts格式转换
ffmpeg系列之mp4与ts格式转换 1. M3U 协议解析 1.1.Tag说明 2.HLS 与 M3U8 3.通过ffmpeg进行转换 3.1. mp4格式转换为ts格式 3.2. ts格式拼接命 ...
- linux下ts转mp4,ffmpeg安装及实现视频格式的转换、分片(ts m3u8)
1.简介 FFmpeg是一套可以用来记录.转换数字音频.视频,并能将其转化为流的开源计算机程序.采用LGPL或GPL许可证.它提供了录制.转换以及流化音视频的完整解决方案 2.相关 系统版本:Cent ...
- FFmpeg 工具:音视频开发都用它,快@你兄弟来看丨音视频工具
(本文基本逻辑:ffmpeg 常用命令介绍 → ffplay 常用命令介绍 → ffprobe 常用命令介绍) 从事音视频开发的程序员几乎都应该知道或使用过 FFmpeg.FFmpeg 是一个开源软件 ...
- 基于FFmpeg 实现RTSP, 音视频编解码,视频流添加文字,音视频合成MP4
前言: 最近闲这没事,整理了一下之前开发过的音视频编解码库,主要基于ffmpeg,实现音视频的编解码.视频流添加文字,音视频同步到MP4等功能.有需要的小伙伴可以参考参考,如果写的有什么不对的地方,欢 ...
- android音视频工程师,音视频学习 (十三) Android 中通过 FFmpeg 命令对音视频编辑处理(已开源)...
## 音视频学习 (十三) Android 中通过 FFmpeg 命令对音视频编辑处理(已开源) ## 视音频编辑器 ## 前言 有时候我们想对音视频进行加工处理,比如视频编辑.添加字幕.裁剪等功能处 ...
最新文章
- centos 安装git_开源物联网MQTT 5.0服务器——EMQ安装及运行教程
- python1000个常用代码-1000个常用的Python库和示例代码
- java 基本数据类型及自己主动类型提升
- Mysql快照读和当前读
- python提取日志内容_Python正则提取日志内容
- 想要你的HTTP稳定不蹦,必须吃透「负载均衡」
- 每一次结束只是一次新的起点,深有体会。
- 1731: [Usaco2005 dec]Layout 排队布局*
- Linux 基础 入门
- 龙芯3A3000上实现BLFS的轻量级桌面LXDE
- qpython3手机版怎么运行不了_QPython3手机版
- linux 卸载oracle库,Linux下完美卸载 Oracle
- 依靠语言和依靠图书馆
- IDEA中文切换回英文
- 『杭电1888』Rectangular Polygons
- (已解决)win环境下 maven 报错:致命错误: 在类路径或引导类路径中找不到程序包 java.lang
- Excel按列合并相同相邻单元格和拆分单元格
- JS的特性:异步 + 事件驱动
- 笔记本使用wifi链接外网,同时有线链接内网详细设置,最后有bat文件,方便随时切换
- 【NA】Householder变换