开发环境
C/C++ 导入Ffmpeg(Version 6.1.1),在程序中调用相关API,非命令行形式调用
需求
当音频数据来源于内存中时,例如源音频是存储在加密文件中,需要读取到内存中解密,然后再用Ffmpeg处理,这样就需要将内存中的数据作为Ffmpeg的数据源。
typedef struct _BufContext {
uint8_t* buf;
uint32_t size;
uint32_t pos;
_BufContext() {
buf = NULL;
size = 0;
pos = 0;
}
} BufContext_TypeDef;
static int read_packet(void *arg, uint8_t *buf, int buf_size)
{
AudioEditorBufContext_TypeDef* fp = (AudioEditorBufContext_TypeDef*) arg;
int64_t remaining = fp->size - fp->pos;
if (remaining <= 0)
{
return AVERROR_EOF; // 数据已读完
}
// 本次读取的字节数(不超过请求的 buf_size)
int read_size = (buf_size < remaining) ? buf_size : (int)remaining;
memcpy(buf, fp->buf + fp->pos, read_size);
fp->pos += read_size; // 更新位置
return read_size;
}
static int64_t seek_packet(void *arg, int64_t offset, int whence) {
int64_t new_pos;
AudioEditorBufContext_TypeDef* fp = (AudioEditorBufContext_TypeDef*) arg;
switch (whence)
{
case SEEK_SET: // 从开头定位
new_pos = offset;
break;
case SEEK_CUR: // 从当前位置定位
new_pos = fp->pos + offset;
break;
case SEEK_END: // 从末尾定位
new_pos = fp->size + offset;
break;
case AVSEEK_SIZE: // 获取总大小
return fp->size;
default:
return -1; // 无效的 whence
}
// 检查新位置是否合法
if (new_pos < 0 || new_pos > fp->size)
{
return -1;
}
fp->pos = new_pos;
return new_pos;
}
int8_t audio_load_from_encode()
{
uint8_t * buf = NULL;
/**
* 因为要从内存中读取数据, avio_context 为手动创建, 故 ffmpeg 不会自动申请缓存区, 所以需要手动管理内存
* buf 作为 ffmpeg 的缓存区, 缓存区长度要足够大(建议64KB起)
* 否则会出现 关键头部信息被截断、频繁触发 read_packet 回调 等问题
*/
uint32_t buf_size = 32768;
AVIOContext* avio_context = NULL;
AVFormatContext* format_context = NULL;
BufContext_TypeDef buf_ctx;
int ret = 0;
FILE* fin = NULL;
/**
* 将文件加载到内存中
*/
fopen_s(&fin, "file_path", "rb");
fseek(fin, 0, SEEK_END);
buf_ctx.size = ftell(fin);
fseek(fin, 0, SEEK_SET);
buf_ctx.buf = (uint8_t*)malloc(buf_ctx.size);
fread(buf_ctx.buf, sizeof(uint8_t), buf_ctx.size, fin);
fclose(fin);
buf_ctx.pos = 0;
// 为 buf 缓存区申请内存, 这里切记要使用 av_malloc 而非标准函数 malloc, 两者内存对齐方式不一样
buf = (uint8_t *)av_malloc(buf_size);
if (buf == NULL)
{
ret = -1;
goto err;
}
/**
* 创建一个 AVIOContext, 使其从内存缓冲区读取数据
* buf 为 ffmpeg 的缓存区, buf_size 是设置的缓存区大小, 在上文中已经讲过
* 0 表示 缓存区只读
* &buf_ctx 为传入的自定义数据, 在 Read 和 Seek 回调函数中作为第一个参数传入
* read_packet 为手动实现的函数, 主要实现将内存中的数据搬移至 ffmpeg 缓存区
* seek_packet 为手动实现的函数, 主要是实现 ffmpeg 可以访问内存中任意地址的数据
* 这里一定要实现 read 回调 和 seek 回调
* ffmpeg 需要具有随机改变地址偏移量, 访问内存的能力, 因为各种信息会存储在不同的位置
* 如果不实现 seek 回调, 那么 ffmpeg 就只能顺序读取数据, 这就造成从数据中解析音频信息缺失的情况
*/
avio_context = avio_alloc_context((unsigned char *)buf, buf_size, 0, &buf_ctx, read_packet, NULL, seek_packet);
if (!avio_context)
{
av_log(NULL, AV_LOG_ERROR, "avio_alloc_context failed\n");
ret = -1;
goto err;
}
/**
* 创建 AVFormatContext 并关联 AVIOContext
* 这里将 format_context.pb 设置为手动创建的 avio_context 后, 在 avformat_open_input 中, 就不会从文件中读取
* 即 传入的第二个参数, 路径将失效, 而是将 avio_context 作为数据源.
*/
format_context = avformat_alloc_context();
if (!format_context)
{
av_log(NULL, AV_LOG_ERROR, "avformat_alloc_context failed\n");
av_free(avio_context);
return 1;
}
format_context->pb = avio_context;
ret = avformat_open_input(&format_context, NULL, NULL, NULL);
if (ret < 0)
{
avformat_free_context(format_context);
return 1;
}
// 获取流信息
ret = avformat_find_stream_info(format_context, NULL);
if (ret < 0)
{
avformat_close_input(&format_context);
return 1;
}
goto end;
err:
end:
if (format_context) avformat_close_input(&format_context);
if (format_context) avformat_free_context(format_context);
if (avio_context) avio_context_free(&avio_context);
if (buf_ctx.buf) free(buf_ctx.buf);
/**
* 释放 avio_context 还有另一种方法, 但是风险会大,
* 仅用于在某些版本的 ffmpeg 中会没有 avio_context_free 的情况
* avio_context 地址可能会发生变化,包括其关联的 buffer 地址也会变化
* 因此,avformat_close_input 在释放时,可能无法释放这部分内存
* 所以最好还是手动释放内存确保内存不会泄露
*/
// if (avio_context)
// {
// av_freep(&avio_context->buffer);
// av_free(avio_context);
// }
return 0;
}