FFmpeg 自定义IO CONTEXT实现音频解码,以及seek函数
对于从音频流buffer中解码的场景中,我们需要实现自己的io context 去从buffer中解码,参考ffmepg官方实例:doc/examples/avio_reading.c
关于是否要实现avio context中的seek函数,需要看需要解码什么格式,大部分格式不需要seek,但是有些格式需要,比如apple开发的ALAC格式,这个格式的音频有的时候它的头文件moov信息是在文件的结尾,这就很坑,一般都是在开头,所以在获取音频的时候需要先seek到文件的结尾,获取moov的信息,然后再seek回来继续解析格式并解码。
关于moov格式的坑:【开发笔记】终于,我们解决了iOS播放器的一个Bug... - 哔哩哔哩
如果你不想实现seek,有没有办法直接把音频文件的moov信息从结尾提到开头呢?也是有的
ffmpeg -i ./old.mp4 -movflags faststart -c copy new.mp4通过这个命令转换后再去解码,文件信息就在开头,就可以不用seek了。
可以通过以下命令去查看头文件信息:
ffprobe -v trace filename直接贴代码:
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavformat/avio.h>
#include <libavutil/file.h>
#include <libavutil/frame.h>
#include <libavutil/mem.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>#define BUF_SIZE 20480FILE *in_file = NULL;struct buffer_data {uint8_t *ptr;uint8_t *ori_ptr;  // for seek file streamsize_t size;       ///< size left in the buffersize_t file_size;  ///< size of the file to decode
};static char *av_get_err(int errnum) {static char err_buf[128] = {0};av_strerror(errnum, err_buf, 128);return err_buf;
}static void print_sample_format(const AVFrame *frame) {printf("ar-samplerate: %uHz\n", frame->sample_rate);printf("ac-channel: %u\n", frame->channels);printf("f-format: %u\n",frame->format);  // 格式需要注意,实际存储到本地文件时已经改成交错模式
}
/*
int read_size;static int read_packet(void *opaque, uint8_t *buf, int buf_size) {//    FILE *in_file = (FILE *)opaque;read_size = fread(buf, 1, buf_size, in_file);printf("read_packet read_size:%d, buf_size:%d\n", read_size, buf_size);if (read_size <= 0) {return AVERROR_EOF;  // 数据读取完毕}return read_size;
}
*/
static int read_packet(void *opaque, uint8_t *buf, int buf_size) {struct buffer_data *bd = (struct buffer_data *)opaque;buf_size = FFMIN(buf_size, bd->size);if (!buf_size) return AVERROR_EOF;//    printf("ptr:%p size:%zu buf_size: %d\n", bd->ptr, bd->size, buf_size);/* copy internal buffer data to buf */memcpy(buf, bd->ptr, buf_size);bd->ptr += buf_size;bd->size -= buf_size;return buf_size;
}// for some format like ALAC (apple format) , which moov partten is located at
// the end of file so we need to implement seek function during demux to seek to
// the end of file for paring the moov info and then seek back to the front
static int64_t seek_packet(void *opaque, int64_t offset, int whence) {//    FILE *in_file = (FILE *)opaque;struct buffer_data *bd = (struct buffer_data *)opaque;int64_t ret = -1;printf("whence=%d , offset=%lld \n", whence, offset);switch (whence) {case AVSEEK_SIZE:printf("AVSEEK_SIZE \n");ret = bd->file_size;break;case SEEK_SET:printf("SEEK_SET \n");bd->ptr = bd->ori_ptr + offset;bd->size = bd->file_size - offset;ret = bd->ptr;break;case SEEK_CUR:printf("SEEK_cur \n");break;case SEEK_END:printf("SEEK_end \n");break;}return ret;
}static void decode(AVCodecContext *dec_ctx, AVPacket *packet, AVFrame *frame,FILE *outfile) {int ret = 0;ret = avcodec_send_packet(dec_ctx, packet);if (ret == AVERROR(EAGAIN)) {printf("Receive_frame and send_packet both returned EAGAIN, which is an API ""violation.\n");} else if (ret < 0) {printf("Error submitting the packet to the decoder, err:%s\n",av_get_err(ret));return;}while (ret >= 0) {ret = avcodec_receive_frame(dec_ctx, frame);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {return;} else if (ret < 0) {printf("Error during decoding\n");exit(1);}if (!packet) {printf("get flush frame\n");}int out_sample_bytes = av_get_bytes_per_sample(dec_ctx->sample_fmt);int out_sample_is_plannar = av_sample_fmt_is_planar(dec_ctx->sample_fmt);// printf("debug %d is out_sample_is_plannar : %d \n", __LINE__,//       out_sample_is_plannar);// print_sample_format(frame);if (out_sample_bytes < 0) {/* This should not occur, checking just for paranoia */fprintf(stderr, "Failed to calculate data size\n");exit(1);}//    printf("debug %d out_sample_bytes: %d samples: %d ch:%d\n", __LINE__,//           out_sample_bytes, frame->nb_samples,//           dec_ctx->ch_layout.nb_channels);if (out_sample_is_plannar) {  // plannar frames/**P表示Planar(平面),其数据格式排列方式为 :LLLLLLRRRRRRLLLLLLRRRRRRLLLLLLRRRRRRL...(每个LLLLLLRRRRRR为一个音频帧)而不带P的数据格式(即交错排列)排列方式为:LRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRL...(每个LR为一个音频样本)播放范例:   ffplay -ar 48000 -ac 2 -f f32le believe.pcm并不是每一种都是这样的格式*/// 这里的写法不是通用,通用要调用重采样的函数去实现// 这里只是针对解码出来是planar格式的转换for (int i = 0; i < frame->nb_samples; i++) {for (int ch = 0; ch < frame->channels; ch++) {// for(int ch = 0; ch < 1; ch++) {fwrite(frame->data[ch] + out_sample_bytes * i, 1, out_sample_bytes,outfile);}}} else  // packed framefwrite(frame->data[0],frame->nb_samples * out_sample_bytes * frame->channels, 1,outfile);}
}int main(int argc, char **argv) {if (argc != 3) {printf("usage: %s <intput file> <out file>\n", argv[0]);return -1;}av_log_set_level(AV_LOG_TRACE);const char *in_file_name = argv[1];const char *out_file_name = argv[2];//    FILE *in_file = NULL;FILE *out_file = NULL;// 1. 打开参数文件in_file = fopen(in_file_name, "rb");if (!in_file) {printf("open file %s failed\n", in_file_name);return -1;}out_file = fopen(out_file_name, "wb+");if (!out_file) {printf("open file %s failed\n", out_file_name);return -1;}struct buffer_data bd = {0};uint8_t *buffer = NULL;size_t buffer_size;int ret = av_file_map(in_file_name, &buffer, &buffer_size, 0, NULL);printf("file size: %d\n", buffer_size);bd.ptr = buffer;bd.ori_ptr = buffer;bd.file_size = buffer_size;bd.size = buffer_size;//    AVInputFormat* in_fmt = av_find_input_format("flac");// 2自定义 iouint8_t *io_buffer = av_malloc(BUF_SIZE);// AVIOContext *avio_ctx = avio_alloc_context(io_buffer, BUF_SIZE, 0, (void// *)in_file,AVIOContext *avio_ctx = avio_alloc_context(io_buffer, BUF_SIZE, 0, &bd,read_packet, NULL, seek_packet);// avio_alloc_context(io_buffer, BUF_SIZE, 0, &bd, read_packet, NULL, NULL);AVFormatContext *format_ctx = avformat_alloc_context();format_ctx->pb = avio_ctx;format_ctx->flags = AVFMT_FLAG_CUSTOM_IO;// int ret = avformat_open_input(&format_ctx, NULL, in_fmt, NULL);// 从输入源读取封装格式文件头ret = avformat_open_input(&format_ctx, NULL, NULL, NULL);if (ret < 0) {printf("avformat_open_input failed:%s\n", av_err2str(ret));return -1;}// 从输入源读取一段数据,尝试解码,以获取流信息if ((ret = avformat_find_stream_info(format_ctx, NULL)) < 0) {av_log(NULL, AV_LOG_ERROR, "Cannot find stream information\n");return ret;}av_dump_format(format_ctx, 0, NULL, 0);int audioStreamIndex =av_find_best_stream(format_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);AVStream *st = format_ctx->streams[audioStreamIndex];// 编码器查找// AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_ALAC);// set codec id from famat paramsAVCodec *codec = avcodec_find_decoder(st->codecpar->codec_id);if (!codec) {printf("avcodec_find_decoder failed\n");return -1;}AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);if (!codec_ctx) {printf("avcodec_alloc_context3 failed\n");return -1;}// copy params from format to codecret = avcodec_parameters_to_context(codec_ctx, format_ctx->streams[audioStreamIndex]->codecpar);if (ret < 0) {printf("Failed to copy in_stream codecpar to codec context\n");}ret = avcodec_open2(codec_ctx, codec, NULL);if (ret < 0) {printf("avcodec_open2 failed:%s\n", av_err2str(ret));return -1;}printf("%d debug codec_ctx->sample_rate: %d\n", __LINE__,codec_ctx->sample_rate);AVPacket *packet = av_packet_alloc();AVFrame *frame = av_frame_alloc();while (1) {ret = av_read_frame(format_ctx, packet);if (ret < 0) {printf("av_read_frame failed:%s\n", av_err2str(ret));break;}decode(codec_ctx, packet, frame, out_file);}printf("read file finish\n");decode(codec_ctx, NULL, frame, out_file);fclose(in_file);fclose(out_file);av_frame_free(frame);av_packet_free(packet);avformat_close_input(&format_ctx);avcodec_free_context(&codec_ctx);printf("main finish\n");return 0;
}
m4a moov 格式解析:MP4格式解析---M4A是MP4中的音频部分_m4a格式解析_一个专研技术的小蜜蜂的博客-CSDN博客
mp4文件格式解析 - 知乎
参考:ffmpeg 利用AVIOContext自定义IO 输出结果写buffer - 知乎
Creating Custom FFmpeg IO-Context - CodeProject
ffmpeg AVIOContext 自定义 IO 及 seek
相关文章:
FFmpeg 自定义IO CONTEXT实现音频解码,以及seek函数
对于从音频流buffer中解码的场景中,我们需要实现自己的io context 去从buffer中解码,参考ffmepg官方实例:doc/examples/avio_reading.c 关于是否要实现avio context中的seek函数,需要看需要解码什么格式,大部分格式不…...
技能升级(2023寒假每日一题 13)
小蓝最近正在玩一款 RPG 游戏。 他的角色一共有 N N N 个可以加攻击力的技能。 其中第 i i i 个技能首次升级可以提升 A i A_i Ai 点攻击力,以后每次升级增加的点数都会减少 B i B_i Bi。 ⌈ A i / B i ⌉ ⌈A_i/B_i⌉ ⌈Ai/Bi⌉(上取整&a…...
 
低频量化之 可转债 配债数据及策略 - 全网独家
目录 历史文章可转债配债数据 待发转债(进展统计)待发转债(行业统计)待发转债(5证监会通过,PE排序)待发转债(5证监会通过,安全垫排序)待发转债(5证…...
 
Code area 和Data area的区别
Code Area FLASH :程序在这个flash运行时,几乎没有延时, 运行速度以时钟设置为准。 Data Area FLASH: 程序在这段flash运行时,每条语句都有延时, 最后的速度可能是以10M为时钟(举例)…...
 
Oracle LiveLabs DB Security (数据库安全)实验汇总
在Oracle LiveLabs中,和数据库安全相关的实验分为2个系列,共12个实验。 Oracle数据库安全架构如下图: 这些实验涉及了Oracle安全相关的特性,企业版选件,独立产品和服务。 关于Oracle安全产品的中文主页可见&#…...
PAT A1012 The Best Rank
1012 The Best Rank 分数 25 作者 CHEN, Yue 单位 浙江大学 To evaluate the performance of our first year CS majored students, we consider their grades of three courses only: C - C Programming Language, M - Mathematics (Calculus or Linear Algrbra), and E -…...
 
“我和AI抠图网站的秘密情缘“
在浏览器里面意外发现了一个AI抠图工,了解了一下,AI抠图基于深度学习框架,结合智能检测识别技术,目前已能够实现高精视,秒级全自动主体、场景像素级识别等的分割能力。 一款好的抠图工具,可以把照片变得更加…...
最多能打多少场比赛呢
凌乱的yyy / 线段覆盖 题目背景 快 noip 了,yyy 很紧张! 题目描述 现在各大 oj 上有 n n n 个比赛,每个比赛的开始、结束的时间点是知道的。 yyy 认为,参加越多的比赛,noip 就能考的越好(假的&#x…...
 
鸿蒙Hi3861学习二-程序烧录与日志输出
一、准备事项 开发板:BearPi-Hm Nano windows工具:HiBurn.exe https://pan.baidu.com/s/18OQD1_BvjNKD_J2e2iX3qg?pwdadrs 提取码:adrs windows工具:MobaXterm和RaiDrive 把ubuntu文件夹映射到windows本地。可以参考如下链接&am…...
typescript Awaited<Type>教程用法
typescript Awaited教程用法 文章目录 typescript Awaited<Type>教程用法 ts4.5发布了Awaited,但是很多人不明白Awaited的用法。 首先看一下官方的说明:这种类型旨在模拟函数await中的操作async,或 s.then()上的方法——特别是它们递归…...
AES硬件运算单元
功能描述 AES单元主要功能如下: 支持解密密钥扩展 支持128bit/192bit/256bit的密钥长度支持ECBCBCCTRM支持DMA进行自动数据传输 支持GF(2^128)域下的乘法,支持GMAC 工作模式 AES有4种工作模式,通过配置MODE1:0]寄存器设置。 模式1:用存储在AES KEYRx寄存…...
mulesoft MCIA 破釜沉舟备考 2023.04.28.26 (易错题)
mulesoft MCIA 破釜沉舟备考 2023.04.28.26 (易错题) 1. According to MuleSoft, what is a major distinguishing characteristic of an application network in relation to the integration of systems, data, and devices?2. An integration team follows MuleSoft’s r…...
 
k210单片机定时器的应用
定时器应该是一个单片机的标准配置,所以k210也是有的,拥有3个定时器,具体的使用方法我们往下看: 分步介绍: 首先是相关模块的使用 构造函数: machine.Timer(id,channel,modeTimer.MODE_ONE_SHOT,period100…...
linux0.12-7-1
[272页] 第7章 初始化程序 1、main.c主要内核初始化工作。 2、如果能完全理解这里调用的所有程序,那么看完这张内容后应该对Linux内核有了大致的了解。 3、 有一定的C语言知识 4、 需要GNU gcc手册在身边作为参考,因为在内核代码很多地方使用gcc的扩展…...
设置 文本框 自动填充背景颜色 为白色
关于autofill伪类的 兼容性: 在现代浏览器中,包括Chrome、Safari、Firefox等,都支持:autofill伪类,但在使用时必须加上浏览器前缀-webkit-,即:-webkit-autofill。 在旧版的浏览器中,可能不支持:autofill伪…...
 
Bitmap引起的OOM问题
作者:向阳逐梦 1.什么是OOM?为什么会引起OOM? 答:Out Of Memory(内存溢出),我们都知道Android系统会为每个APP分配一个独立的工作空间,或者说分配一个单独的Dalvik虚拟机,这样每个APP都可以独立…...
 
【JavaEE初阶】认识线程(Thread)
目录 🌾 前言 🌾 了解线程 🌈1.1 线程是什么? 🌈1.2 一些基本问题 🌾2、创建线程的方式 🌈 2.1 继承Thread类 🌈 2.2 实现Runnable接口并重写run()方法 🌈 注意…...
 
自动化运维工具一Ansible Roles实战
目录 一、Ansible Roles概述 1.1.roles官方的目录结构 1.2.Ansible Roles依赖关系 二、Ansible Roles案例实战 2.1.Ansible Roles NFS服务 2.2 Roles Memcached 2.3 Roles-rsync服务 一、Ansible Roles概述 之前介绍了 Playbook 的使用方法,对于批量任务的部…...
json 中有递归parentId节点转 c#实体类时如何处理
如果您有一个具有递归parentId节点的JSON数据,并且您需要将其转换为C#实体类,则可以使用以下方法: 创建一个类来表示JSON对象的节点,包括它的属性和子节点。 public class Node {public int Id { get; set; }public string Name …...
 
给大家介绍几个手机冷门但好用的小技巧
技巧一:拍照识别植物 手机的拍照识别植物功能是指在使用手机相机时,可以通过对植物进行拍照,并通过植物识别技术,获取植物的相关信息和资料。其主要优点如下: 方便实用:使用拍照识别植物功能,…...
 
UE5 学习系列(二)用户操作界面及介绍
这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…...
 
突破不可导策略的训练难题:零阶优化与强化学习的深度嵌合
强化学习(Reinforcement Learning, RL)是工业领域智能控制的重要方法。它的基本原理是将最优控制问题建模为马尔可夫决策过程,然后使用强化学习的Actor-Critic机制(中文译作“知行互动”机制),逐步迭代求解…...
 
微软PowerBI考试 PL300-选择 Power BI 模型框架【附练习数据】
微软PowerBI考试 PL300-选择 Power BI 模型框架 20 多年来,Microsoft 持续对企业商业智能 (BI) 进行大量投资。 Azure Analysis Services (AAS) 和 SQL Server Analysis Services (SSAS) 基于无数企业使用的成熟的 BI 数据建模技术。 同样的技术也是 Power BI 数据…...
 
工业安全零事故的智能守护者:一体化AI智能安防平台
前言: 通过AI视觉技术,为船厂提供全面的安全监控解决方案,涵盖交通违规检测、起重机轨道安全、非法入侵检测、盗窃防范、安全规范执行监控等多个方面,能够实现对应负责人反馈机制,并最终实现数据的统计报表。提升船厂…...
【解密LSTM、GRU如何解决传统RNN梯度消失问题】
解密LSTM与GRU:如何让RNN变得更聪明? 在深度学习的世界里,循环神经网络(RNN)以其卓越的序列数据处理能力广泛应用于自然语言处理、时间序列预测等领域。然而,传统RNN存在的一个严重问题——梯度消失&#…...
在Ubuntu中设置开机自动运行(sudo)指令的指南
在Ubuntu系统中,有时需要在系统启动时自动执行某些命令,特别是需要 sudo权限的指令。为了实现这一功能,可以使用多种方法,包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法,并提供…...
今日科技热点速览
🔥 今日科技热点速览 🎮 任天堂Switch 2 正式发售 任天堂新一代游戏主机 Switch 2 今日正式上线发售,主打更强图形性能与沉浸式体验,支持多模态交互,受到全球玩家热捧 。 🤖 人工智能持续突破 DeepSeek-R1&…...
 
【JavaWeb】Docker项目部署
引言 之前学习了Linux操作系统的常见命令,在Linux上安装软件,以及如何在Linux上部署一个单体项目,大多数同学都会有相同的感受,那就是麻烦。 核心体现在三点: 命令太多了,记不住 软件安装包名字复杂&…...
代理篇12|深入理解 Vite中的Proxy接口代理配置
在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...
docker 部署发现spring.profiles.active 问题
报错: org.springframework.boot.context.config.InvalidConfigDataPropertyException: Property spring.profiles.active imported from location class path resource [application-test.yml] is invalid in a profile specific resource [origin: class path re…...
