FFmpeg AAC 解码 PCM
1. 概要与流程图
1.1 AAC 转 PCM,需要解码库来实现,目前了解有三种方式,当前使用的是 FFmpeg 库解码
1) faad 解码库网址:faadhttps://sourceforge.net/projects/faac/files/faad2-src/
2) fdk-aac 编解码库网址: fdk-aachttps://sourceforge.net/projects/opencore-amr/files/fdk-aac/
3) FFmpeg 网址:ffmpeghttp://ffmpeg.org/download.html
1.2 流程图:
2. 封装解码管理器
2.1 创建头文件,AACToPCMDeCoder.h
-
-
-
NS_ASSUME_NONNULL_BEGIN
-
-
@interface AACToPCMDeCoder : NSObject
-
-
- (int) init:(const char *)inputFilePath outputFilePath:(const char *)outputFilePath;
-
-
- (void)convertToPCM:(const uint8_t*)data withSize:(int) size;
-
-
- (void)stop;
-
-
@end
-
-
NS_ASSUME_NONNULL_END
2.2 实现头文件,AACToPCMDeCoder.m
-
-
-
-
-
-
-
-
@interface AACToPCMDeCoder(){
-
AVFormatContext *format_ctx;
-
AVCodecContext *codec_ctx;
-
AVCodec *codec;
-
AVPacket *packet;
-
struct SwrContext *swr_ctx;
-
AVCodecParserContext *codec_parser_ctx;
-
AVFrame *frame_src;
-
FileTool *file_tool;
-
uint8_t *buffer_src;
-
int nb_samples;
-
int buffer_size;
-
}
-
-
@end
-
-
@implementation AACToPCMDeCoder
-
-
/// 初始化
-
/// - Parameters:
-
/// - inputFilePath: 输入文件路近
-
/// - outputFilePath: 输出文件路径
-
- (int)init:(const char *)inputFilePath outputFilePath:(const char *)outputFilePath{
-
format_ctx = NULL;
-
codec = NULL;
-
codec_ctx = NULL;
-
codec_parser_ctx = NULL;
-
frame_src = NULL;
-
swr_ctx = NULL;
-
file_tool = NULL;
-
buffer_src = NULL;
-
nb_samples = 0;
-
buffer_size = 0;
-
// 获取输入文件的 AVFormatContext
-
int ret = avformat_open_input(&format_ctx, inputFilePath, NULL, NULL);
-
if(ret < 0){
-
fprintf(stderr, "avformat_open_input function open failed. \n");
-
return ret;
-
}
-
// 获取流信息
-
ret = avformat_find_stream_info(format_ctx, NULL);
-
if(ret < 0){
-
fprintf(stderr, "avformat_find_stream_info function find failed. \n");
-
return ret;
-
}
-
// 获取音频流下标
-
int index = av_find_best_stream(format_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
-
if(index < 0){
-
fprintf(stderr, "av_find_best_stream function find strean failed. \n");
-
return -1;
-
}
-
// 获取解码器上下文
-
codec_ctx = avcodec_alloc_context3(NULL);
-
if(!codec_ctx){
-
fprintf(stderr, "avcodec_alloc_context3 function failed. \n");
-
return -1;
-
}
-
-
//codec_ctx->pkt_timebase = format_ctx->streams[index]->time_base;
-
// 视频流信息赋值到 CodecContext stream->codecpar
-
ret = avcodec_parameters_to_context(codec_ctx, format_ctx->streams[index]->codecpar);
-
if(ret < 0){
-
fprintf(stderr, "avcodec_parameters_to_context function failed. \n");
-
return ret;
-
}
-
// 查找解码器
-
codec = avcodec_find_decoder(codec_ctx->codec_id);
-
if(!codec){
-
fprintf(stderr, "avcodec_find_decoder function find failed. \n");
-
return -1;
-
}
-
-
// 打开解码器
-
ret = avcodec_open2(codec_ctx, codec, NULL);
-
if(ret < 0){
-
fprintf(stderr, "avcodec_open2 function open failed. \n");
-
return ret;
-
}
-
-
// 初始化 Codec 内容解析器,用于传输的数据缓存
-
codec_parser_ctx = av_parser_init(codec_ctx->codec_id);
-
if(!codec_parser_ctx){
-
fprintf(stderr, "av_parser_init function failed. \n");
-
return -1;
-
}
-
-
// 初始化传输的数据整理为包
-
packet = av_packet_alloc();
-
if(!packet){
-
fprintf(stderr, "av_packet_alloc function failed. \n");
-
return -1;
-
}
-
-
// 初始化 frame,用于存储解码后的数据
-
frame_src = av_frame_alloc();
-
if(!frame_src){
-
fprintf(stderr, "av_frame_alloc function frame failed. \n");
-
return -1;
-
}
-
frame_src->format = AV_SAMPLE_FMT_S16; // 采样格式 AV_SAMPLE_FMT_S16
-
frame_src->channels = codec_ctx->channels; // 通道数
-
frame_src->channel_layout = codec_ctx->channel_layout; // 声道布局
-
frame_src->sample_rate = codec_ctx->sample_rate; // 采样率
-
frame_src->nb_samples = codec_ctx->frame_size; // 样本
-
ret = av_frame_get_buffer(frame_src, 0); // 获取 s16 格式帧数据
-
if(ret < 0){
-
fprintf(stderr, "frame_s16 av_frame_get_buffer function failed \n");
-
return ret;
-
}
-
// 转换写文件的 frame_s16->nb_samples * av_get_bytes_per_sample(frame_s16->format) * frame_s16->channels
-
// 解码后 pcm 大小 buffer 4096
-
buffer_size = av_samples_get_buffer_size(&nb_samples, frame_src->channels, frame_src->nb_samples, frame_src->format, 0);
-
// 解码一帧的数据
-
buffer_src = av_malloc(buffer_size);
-
-
// 获取 samples 4096
-
// nb_samples = av_samples_alloc(&buffer_src, NULL, frame_src->channels, frame_src->nb_samples, frame_src->format, 0);
-
-
// 重采样
-
// 获取转换帧内容
-
swr_ctx = swr_alloc_set_opts(NULL,
-
frame_src->channel_layout,
-
frame_src->format,
-
frame_src->sample_rate,
-
codec_ctx->channel_layout,
-
codec_ctx->sample_fmt,
-
codec_ctx->sample_rate,
-
0, NULL);
-
ret = swr_init(swr_ctx);
-
if(ret < 0){
-
fprintf(stderr, "sws_getContext function failed. \n");
-
return ret;
-
}
-
-
// 创建输出文件
-
file_tool = [[FileTool alloc]init];
-
// 打开输出文件
-
ret = (int)[file_tool open:outputFilePath model: FILE_MODE_WRITE];
-
if(ret < 0){
-
fprintf(stderr, "Open output file failed. \n");
-
return ret;
-
}
-
-
// 打印输入流信息
-
av_dump_format(format_ctx, 0, inputFilePath, 0);
-
// 打印输出流信息
-
printf("Output\n%s\n",outputFilePath);
-
printf("AACToPCMDeCoder init success.\n");
-
return ret;
-
}
-
-
/// 解码
-
/// - Parameters:
-
/// - data: 数据 ( AAC)
-
/// - size: 数据大小
-
- (void)convertToPCM:(const uint8_t *)data withSize:(int)size{
-
int current_size = size;
-
while (current_size > 0) {
-
int parser_size = av_parser_parse2(codec_parser_ctx, codec_ctx, &packet->data, &packet->size,
-
data, current_size, AV_NOPTS_VALUE, AV_NOPTS_VALUE, AV_NOPTS_VALUE);
-
data = parser_size;
-
current_size -= parser_size;
-
// 判断不是一帧数据,则继续
-
if(!packet->size){
-
continue;
-
}else{
-
int ret = avcodec_send_packet(codec_ctx, packet);
-
if(ret == 0){
-
ret = avcodec_receive_frame(codec_ctx, frame_src);
-
// 编码成功
-
if(ret == 0){
-
//解码后的帧进行转换,存储
-
[self convertToPCMData:frame_src];
-
}
-
}
-
av_packet_unref(packet);
-
}
-
}
-
}
-
-
/// 转换 PCM 数据,写入文件
-
/// - Parameter frame: 解码得到的帧
-
-(void)convertToPCMData:(AVFrame *)frame{
-
// 转换数据
-
int ret = swr_convert(swr_ctx, &buffer_src, nb_samples, (const uint8_t **)frame->data, frame->nb_samples);
-
if(ret <= 0){
-
fprintf(stderr, "swr_convert function failed. \n");
-
}
-
// 写入文件
-
[file_tool write:buffer_src length:buffer_size];
-
}
-
-
/// 停止
-
- (void)stop{
-
// 退出循环解码时,看解码器中是否还有缓存数据,有数据继续执行解码
-
while (true) {
-
int ret = avcodec_send_packet(codec_ctx, packet);
-
if(ret != 0){
-
break;
-
}
-
ret = avcodec_receive_frame(codec_ctx, frame_src);
-
if(ret == 0){
-
//解码后的帧进行转换,存储
-
[self convertToPCMData:frame_src];
-
}
-
av_packet_unref(packet);
-
}
-
}
-
-
/// 关闭置空数据
-
- (void)close{
-
if(codec_parser_ctx){
-
av_parser_close(codec_parser_ctx);
-
codec_parser_ctx = NULL;
-
}
-
if(codec_ctx){
-
avcodec_close(codec_ctx);
-
avcodec_free_context(&codec_ctx);
-
codec_ctx = NULL;
-
}
-
if(swr_ctx){
-
swr_free(&swr_ctx);
-
swr_ctx = NULL;
-
}
-
if(format_ctx){
-
if(format_ctx->pb){
-
avio_close(format_ctx->pb);
-
}
-
//avformat_close_input(&format_ctx);
-
avformat_free_context(format_ctx);
-
format_ctx = NULL;
-
}
-
if(packet){
-
av_packet_free(&packet);
-
packet = NULL;
-
}
-
if(frame_src){
-
av_frame_free(&frame_src);
-
frame_src = NULL;
-
}
-
if(file_tool){
-
file_tool = NULL;
-
}
-
if(buffer_src){
-
av_free(buffer_src);
-
buffer_src = NULL;
-
}
-
printf("AACToPCMDeCoder stop.\n");
-
}
-
-
// 释放数据
-
- (void)dealloc{
-
[self close];
-
}
-
-
@end
3. 测试函数及输出日志
3.1 测试函数 onClickAACToPCM,ViewController.m
-
-
-
-
-
-
-
-
-
-
-
@interface ViewController ()<YUVPlayerDelegate>
-
@property(nonatomic, strong) UIImageView *imageView;
-
@end
-
-
@implementation ViewController
-
@synthesize imageView;
-
-
- (void)viewDidLoad {
-
[super viewDidLoad];
-
}
-
-
/// 分离视频文件
-
/// - Parameter sender: UI 按钮
-
- (IBAction)onClickMediaFileSeparation:(id)sender {
-
// 输入文件
-
NSString * inFilePath = [PathTool bundlePath:@"input.mp4"];
-
// 输出文件(一般 mp4 音频流为 aac 格式 "aac" = "adts")
-
NSString * outFilePathAudio = [PathTool documentsPath:@"output"];
-
// 输出文件(一般 mp4 视频流为 h264 或者 hevc)
-
NSString * outFilePathVideo = [PathTool documentsPath:@"output"];
-
// 创建分离视频文件类
-
__block MediaFileSeparation *mediaFileSeparation = [[MediaFileSeparation alloc] init];
-
// 初始化数据,传入输入输出文件
-
int ret = [mediaFileSeparation init: inFilePath.UTF8String videoFilePath: (char *)outFilePathAudio.UTF8String audioFilePath: (char *)outFilePathVideo.UTF8String];
-
// 当返回值小于 0 时,初始化失败
-
if(ret < 0){
-
return;
-
}
-
dispatch_async(dispatch_get_global_queue(DISPATCH_BLOCK_DETACHED, 0), ^{
-
// 循环拆分数据
-
[mediaFileSeparation convertFrames];
-
// 结束
-
[mediaFileSeparation stop];
-
// 释放数据
-
mediaFileSeparation = nil;
-
});
-
}
-
-
/// H264 解码 YUV
-
/// - Parameter sender: UI 按钮
-
- (IBAction)onClickH264ToYUV:(id)sender {
-
// 输入文件
-
NSString *inputFilePath = [PathTool documentsPath:@"output.hevc"];
-
// 输出文件
-
NSString *outputFilePath = [PathTool documentsPath:@"output.yuv"];
-
// 初始化文件管理器
-
__block FileTool *fileTool = [[FileTool alloc] init];
-
// 打开文件
-
int ret = (int)[fileTool open:inputFilePath.UTF8String model: FILE_MODE_READ];
-
// 小于 0,打开文件异常
-
if(ret < 0){
-
return;
-
}
-
// 初始化 H264 解码 YUV 管理器
-
__block H264ToYUVDeCoder *h264ToYUVDeCoder = [[H264ToYUVDeCoder alloc]init];
-
// 传入输入输出文件路径
-
ret = [h264ToYUVDeCoder init:inputFilePath.UTF8String outputFilePath:outputFilePath.UTF8String];
-
// 当返回值小于 0 时,初始化失败
-
if(ret < 0){
-
return;
-
}
-
//Stream #0:0: Video: hevc, yuv420p(tv), 848x480, 25 fps, 25 tbr, 1200k tbn, 25 tbc
-
// __weak ViewController *vc = self;
-
// 开启子线程,进行解码操作
-
dispatch_async(dispatch_get_global_queue(DISPATCH_BLOCK_DETACHED, 0), ^{
-
// 定义输入读取数据大小
-
int length = 1024 * 6;
-
uint8_t frame[length];
-
while(true){
-
// 读取输入数据
-
int size = (int)[fileTool read:frame length:length];
-
// 读完文件,则跳出循环
-
if(size <= 0){
-
break;
-
}
-
// 解码数据
-
[h264ToYUVDeCoder convertToYUV:frame withSize:length];
-
}
-
// 停止
-
[h264ToYUVDeCoder stop];
-
// 释放解码器数据
-
h264ToYUVDeCoder = nil;
-
// 释放文件资源
-
fileTool = nil;
-
});
-
}
-
-
/// YUV 编码 H264
-
/// - Parameter sender: UI 按钮
-
- (IBAction)onClickYUVToH264:(id)sender {
-
// 输入文件
-
NSString *inputFilePath = [PathTool documentsPath:@"output.yuv"];
-
// 输出文件
-
NSString *outputFilePath = [PathTool documentsPath:@"output.h264"];
-
// 初始化文件管理器
-
__block FileTool *fileTool = [[FileTool alloc]init];
-
// 打开文件
-
int ret = (int)[fileTool open:inputFilePath.UTF8String model:FILE_MODE_READ];
-
// 当返回值小于 0 时,初始化失败
-
if(ret < 0){
-
return;
-
}
-
// 初始化 YUV 编码 H264 管理器
-
__block YUVToH264EnCoder *yuvToH264EnCoder = [[YUVToH264EnCoder alloc]init];
-
// 传入输出文件路径,宽和高
-
ret = [yuvToH264EnCoder init: outputFilePath.UTF8String width:848 height:480];
-
// 当返回值小于 0 时,初始化失败
-
if(ret < 0){
-
return;
-
}
-
// 开启子线程,进行编码操作
-
dispatch_async(dispatch_get_global_queue(DISPATCH_BLOCK_DETACHED, 0), ^{
-
// 定义输入读取数据大小,如果根据初始化得到的 yuv 数据大小,读取 610560 个数据点,fread 会卡住,只能分段读取进行缓存处理
-
int length = 1024 * 8;
-
uint8_t frame[length];
-
while (true) {
-
// 读取输入数据
-
int size = (int)[fileTool read:frame length:length];
-
// 读完文件,则跳出循环
-
if(size <= 0){
-
break;
-
}
-
[yuvToH264EnCoder convertCacheData:frame withSize:size];
-
}
-
// 停止
-
[yuvToH264EnCoder stop];
-
// 释放解码器数据
-
yuvToH264EnCoder = nil;
-
// 释放文件资源
-
fileTool = nil;
-
});
-
}
-
/// 播放 YUV
-
/// - Parameter sender: UI 按钮
-
- (IBAction)onClickYUVPlayer:(id)sender {
-
// 输入文件
-
NSString *inputFilePath = [PathTool documentsPath:@"output.yuv"];
-
// 初始化文件管理器
-
__block FileTool *fileTool = [[FileTool alloc] init];
-
// 打开文件
-
int ret = (int)[fileTool open:inputFilePath.UTF8String model:FILE_MODE_READ];
-
// 返回值小于 0,打开文件失败
-
if(ret < 0){
-
return;
-
}
-
// 初始化 YUV 转 RGB 管理器
-
__block YUVPlayer *yuvPlayer = [[YUVPlayer alloc]init];
-
// delegate 回调
-
yuvPlayer.delegate = self;
-
// 添加 ImageView,用来播放画面
-
[self setupImageView];
-
// 传入宽高
-
ret = [yuvPlayer init:848 height:480];
-
// 返回值小于 0,初始化失败
-
if(ret < 0){
-
return;
-
}
-
// 开启子线程进行转码渲染
-
dispatch_async(dispatch_get_global_queue(DISPATCH_BLOCK_DETACHED, 0), ^{
-
// 定义输入读取数据大小
-
int length = 1024 * 8;
-
uint8_t frame[length];
-
while (true) {
-
// 读取输入数据
-
int size = (int)[fileTool read:frame length:length];
-
// 读完文件,则跳出循环
-
if(size <= 0){
-
break;
-
}
-
// 缓存并播放数据
-
[yuvPlayer cacheData:frame withSize:size];
-
}
-
// 释放播放器数据
-
yuvPlayer = nil;
-
// 释放文件资源
-
fileTool = nil;
-
});
-
}
-
-
// ImageView
-
-(void)setupImageView{
-
if(!imageView){
-
CGFloat width = 320;
-
CGFloat height = 180;
-
CGFloat x = (self.view.width - width) * 0.5;
-
CGFloat y = (self.view.height - height) * 0.85;
-
// 显示画面
-
imageView = [[UIImageView alloc]initWithFrame:CGRectMake(x, y, width, height)];
-
imageView.backgroundColor = UIColor.blackColor;
-
imageView.contentMode = UIViewContentModeScaleToFill;
-
[self.view addSubview:imageView];
-
}
-
}
-
-
// 更新 Image
-
- (void)showImage:(UIImage *)image{
-
dispatch_async(dispatch_get_main_queue(), ^{
-
self.imageView.image = image;
-
});
-
}
-
-
/// AAC 解码 PCM
-
/// - Parameter sender: UI 按钮
-
- (IBAction)onClickAACToPCM:(id)sender {
-
// 输入文件
-
NSString *inputFilePath = [PathTool documentsPath:@"output.aac"];
-
// 输出文件
-
NSString *outputFilePath = [PathTool documentsPath:@"output.pcm"];
-
// 初始化文件管理器
-
__block FileTool *fileTool = [[FileTool alloc]init];
-
// 打开文件
-
int ret = (int)[fileTool open:inputFilePath.UTF8String model:FILE_MODE_READ];
-
// 返回值小于 0,打开文件异常
-
if(ret < 0){
-
return;
-
}
-
// 初始化 AAC 解码 PCM 管理器
-
__block AACToPCMDeCoder *aacToPCMDeCoder =[[AACToPCMDeCoder alloc]init];
-
// 传入输入输出文件路径
-
ret = [aacToPCMDeCoder init:inputFilePath.UTF8String outputFilePath:outputFilePath.UTF8String];
-
// 返回值小于 0 时,初始化失败
-
if(ret < 0){
-
return;
-
}
-
// 开启子线程,进行解码操作
-
dispatch_async(dispatch_get_global_queue(DISPATCH_BLOCK_DETACHED, 0), ^{
-
// 定义输入读取数据大小
-
int length = 1024 * 4;
-
uint8_t data[length];
-
while (true) {
-
// 读取输入数据
-
int size = (int)[fileTool read:data length:length];
-
// 读完文件,则跳出循环
-
if(size <= 0){
-
break;
-
}
-
// 解码数据
-
[aacToPCMDeCoder convertToPCM:data withSize:size];
-
}
-
// 停止
-
[aacToPCMDeCoder stop];
-
// 释放解码器数据
-
aacToPCMDeCoder = nil;
-
// 释放文件资源
-
fileTool = nil;
-
});
-
}
-
-
@end
3.2 输出执行成功日志:
-
[aac @ 0x7fc1d080a400] Estimating duration from bitrate, this may be inaccurate
-
Input #0, aac, from '/Users/lihanyang/Library/Developer/CoreSimulator/Devices/B317C1BE-A990-4626-A914-C372CA6B59C2/data/Containers/Data/Application/A4D1100F-BF59-4AB3-A573-1E09A34E0EFB/Documents/output.aac':
-
Duration: 00:03:19.74, bitrate: 52 kb/s
-
Stream #0:0: Audio: aac, 44100 Hz, stereo, fltp, 52 kb/s
-
Output
-
/Users/lihanyang/Library/Developer/CoreSimulator/Devices/B317C1BE-A990-4626-A914-C372CA6B59C2/data/Containers/Data/Application/A4D1100F-BF59-4AB3-A573-1E09A34E0EFB/Documents/output.pcm
-
AACToPCMDeCoder init success.
-
AACToPCMDeCoder stop.
这篇好文章是转载于:学新通技术网
- 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
- 本站站名: 学新通技术网
- 本文地址: /boutique/detail/tanhfhjkkg
系列文章
更多
同类精品
更多
-
photoshop保存的图片太大微信发不了怎么办
PHP中文网 06-15 -
《学习通》视频自动暂停处理方法
HelloWorld317 07-05 -
Android 11 保存文件到外部存储,并分享文件
Luke 10-12 -
word里面弄一个表格后上面的标题会跑到下面怎么办
PHP中文网 06-20 -
photoshop扩展功能面板显示灰色怎么办
PHP中文网 06-14 -
微信公众号没有声音提示怎么办
PHP中文网 03-31 -
excel下划线不显示怎么办
PHP中文网 06-23 -
excel打印预览压线压字怎么办
PHP中文网 06-22 -
TikTok加速器哪个好免费的TK加速器推荐
TK小达人 10-01 -
怎样阻止微信小程序自动打开
PHP中文网 06-13