由于当前的一些项目需要部署到微服务中,许多数据已经保存于内存中,因此有些数据并不能直接通过常规的读取文件接口进行处理,需要从内存中读取数据。
如果只是参考ffmpeg的doc/examples/avio_reading.c
中的代码读取内存数据,在解码时可能会出现警告:
[mp3 @ 0x55a2f51b89c0] invalid concatenated file detected - using bitrate for duration
并且解码后的数据,在某些格式下可能会在末尾处丢失或多出一部分数据。
以音频解码得到的PCM数据为例,前面部分解码的数据是一致的,但是末尾部分,内存解码会与ffmpeg结果不一致,还多出一部分垃圾数据:
经过查找,最后在StackOverflow中找到原因。这是由于在avio_alloc_context
时没有实现自定义函数int64_t (*seek)(void *opaque, int64_t offset, int whence))
导致的。而网上的许多从内存读取数据解码的教程,大多是参考雷神和avio_reading.c的代码,但是这两份代码都没有提到和实现内存seek回调函数(雷神的代码是file.read,文件流自带seek)。实现自定的seek后再解码,得到的PCM数据与ffmpeg相同:
参考的源代码如下:
struct MemoryAVFormat {
MemoryAVFormat(const MemoryAVFormat &) = delete;
AVFormatContext *ctx;
AVIOContext *ioCtx;
char *audio;
size_t audio_length;
size_t audio_offset;
MemoryAVFormat(char *theAudio, size_t theAudioLength)
: ctx(avformat_alloc_context()),
ioCtx(nullptr),
audio(theAudio),
audio_length(theAudioLength),
audio_offset(0) {
ioCtx = create_audio_buffer_io_context();
if (ctx == nullptr)
throw audio_processing_exception("Failed to allocate context");
if (ioCtx == nullptr)
throw audio_processing_exception("Failed to allocate IO context for audio buffer");
ctx->pb = ioCtx;
ctx->flags |= AVFMT_FLAG_CUSTOM_IO;
int err = avformat_open_input(&ctx, "nullptr", NULL, NULL);
if (err != 0)
throwAvError("Error configuring context from audio buffer", err);
}
int read (uint8_t* theBuf, int theBufSize) {
int aNbRead = std::min (int(audio_length - audio_offset), theBufSize);
if(aNbRead == 0) { return AVERROR_EOF; }
memcpy(theBuf, audio + audio_offset, aNbRead);
audio_offset += aNbRead;
return aNbRead;
}
int64_t seek(int64_t offset, int whence) {
if (whence == AVSEEK_SIZE) { return audio_length; }
audio_offset = offset;
if(audio == NULL || audio_length == 0) { return -1; }
if (whence == SEEK_SET) { audio_offset = offset; }
else if(whence == SEEK_CUR) { audio_offset += offset; }
else if(whence == SEEK_END) { audio_offset = audio_length + offset; }
//if(audio_offset < 0) { audio_offset = 0; } else
//if(audio_offset > audio_length) { audio_offset = audio_length; }
return offset;
}
AVIOContext *create_audio_buffer_io_context() {
const int aBufferSize = 4096;
unsigned char* aBufferIO = (unsigned char* )av_malloc(aBufferSize + AV_INPUT_BUFFER_PADDING_SIZE);
return avio_alloc_context(aBufferIO,
aBufferSize,
0,
this,
[](void* opaque, uint8_t* buf, int bufSize)
{ return ((MemoryAVFormat* )opaque)->read(buf, bufSize); },
NULL,
[](void* opaque, int64_t offset, int whence)
{ return ((MemoryAVFormat* )opaque)->seek(offset, whence); });
}
~MemoryAVFormat() {
av_free(ioCtx);
avformat_close_input(&ctx);
}
}