我正在尝试使用自定义上下文解码视频。目的是我想直接从内存中解码视频。在下面的代码中,我正在传递给read
的{{1}}函数中的文件中读取 - 但这仅用于测试目的。
我想我已经阅读过Stackoverflow或与此主题相关的任何其他网站上的任何帖子。至少我绝对尽力这样做。虽然有很多共同点,但细节有所不同:人们设置不同的标志,有些人说avio_alloc_context
是必需的,有些人说不是,等等。由于某些原因,对我来说没有任何作用。
我的问题是像素格式未指定(请参阅下面的输出),这就是我在以后调用av_probe_input_format
时遇到问题的原因。我检查了sws_getContext
,它是-1。
请在代码中注明我的评论pFormatContext->streams[videoStreamIndex]->codec->pix_fmt
和// things I tried
。我想,答案可能会隐藏在那里。到目前为止,我尝试了许多提示组合,但我想错了一个细节。
问题不在于视频文件,因为当我采用标准方式而只是在没有自定义上下文的情况下调用// seems not to help
时,一切运行正常。
代码按原样编译和运行。
avformat_open_input(&pFormatContext, pFilePath, NULL, NULL)
这是我得到的输出:
readBytes = 32768
检索流信息...
[mov,mp4,m4a,3gp,3g2,mj2 @ 0xdf8d20]流0,偏移量0x30:部分文件
[mov,mp4,m4a,3gp,3g2,mj2 @ 0xdf8d20]无法找到流0的编解码器参数(视频:h264(avc1 / 0x31637661),无,640x360,351 kb / s):未指定的像素格式
考虑增加'analyzeuration'和'probesize'选项的值
nb_streams = 2
更新:
感谢WLGfx,这里有解决方案:唯一缺少的是搜索功能。显然,实现它是解码的必要条件。重要的是返回新的偏移量 - 如果成功则不返回0(在Web中找到的某些解决方案只返回fseek的返回值,这是错误的)。以下是使其起作用的最小解决方案:
#include <libavformat/avformat.h>
#include <string.h>
#include <stdio.h>
FILE *f;
static int read(void *opaque, uint8_t *buf, int buf_size) {
if (feof(f)) return -1;
return fread(buf, 1, buf_size, f);
}
int openVideo(const char *pFilePath) {
const int bufferSize = 32768;
int ret;
av_register_all();
f = fopen(pFilePath, "rb");
uint8_t *pBuffer = (uint8_t *) av_malloc(bufferSize + AVPROBE_PADDING_SIZE);
AVIOContext *pAVIOContext = avio_alloc_context(pBuffer, bufferSize, 0, NULL,
&read, NULL, NULL);
if (!f || !pBuffer || !pAVIOContext) {
printf("error: open / alloc failed\n");
// cleanup...
return 1;
}
AVFormatContext *pFormatContext = avformat_alloc_context();
pFormatContext->pb = pAVIOContext;
const int readBytes = read(NULL, pBuffer, bufferSize);
printf("readBytes = %i\n", readBytes);
if (readBytes <= 0) {
printf("error: read failed\n");
// cleanup...
return 2;
}
if (fseek(f, 0, SEEK_SET) != 0) {
printf("error: fseek failed\n");
// cleanup...
return 3;
}
// required for av_probe_input_format
memset(pBuffer + readBytes, 0, AVPROBE_PADDING_SIZE);
AVProbeData probeData;
probeData.buf = pBuffer;
probeData.buf_size = readBytes;
probeData.filename = "";
probeData.mime_type = NULL;
pFormatContext->iformat = av_probe_input_format(&probeData, 1);
// things I tried:
//pFormatContext->flags = AVFMT_FLAG_CUSTOM_IO;
//pFormatContext->iformat->flags |= AVFMT_NOFILE;
//pFormatContext->iformat->read_header = NULL;
// seems not to help (therefore commented out here):
AVDictionary *pDictionary = NULL;
//av_dict_set(&pDictionary, "analyzeduration", "8000000", 0);
//av_dict_set(&pDictionary, "probesize", "8000000", 0);
if ((ret = avformat_open_input(&pFormatContext, "", NULL, &pDictionary)) < 0) {
char buffer[4096];
av_strerror(ret, buffer, sizeof(buffer));
printf("error: avformat_open_input failed: %s\n", buffer);
// cleanup...
return 4;
}
printf("retrieving stream information...\n");
if ((ret = avformat_find_stream_info(pFormatContext, NULL)) < 0) {
char buffer[4096];
av_strerror(ret, buffer, sizeof(buffer));
printf("error: avformat_find_stream_info failed: %s\n", buffer);
// cleanup...
return 5;
}
printf("nb_streams = %i\n", pFormatContext->nb_streams);
// further code...
// cleanup...
return 0;
}
int main() {
openVideo("video.mp4");
return 0;
}
当然,对static int64_t seek(void *opaque, int64_t offset, int whence) {
if (whence == SEEK_SET && fseek(f, offset, SEEK_SET) == 0) {
return offset;
}
// handling AVSEEK_SIZE doesn't seem mandatory
return -1;
}
的调用需要相应调整:
avio_alloc_context
答案 0 :(得分:2)
看到你的是基于文件的流然后它是可搜索的,所以你可以在创建AVIOContext时提供AVIO搜索:
avioContext = avio_alloc_context((uint8_t *)avio_buffer, AVIO_QUEUE_SIZE * PKT_SIZE7,
0,
this, // *** This is your data pointer to a class or other data passed to the callbacks
avio_ReadFunc,
NULL,
avio_SeekFunc);
使用此回调处理搜索:(您可以将ptr
投射到您的班级或其他数据结构中)
int64_t FFIOBufferManager::avio_SeekFunc(void *ptr, int64_t pos64, int whence) {
// SEEK_SET(0), SEEK_CUR(1), SEEK_END(2), AVSEEK_SIZE
// ptr is cast to your data or class
switch (whence) {
case 0 : // SEEK_SET
... etc
case (AVSEEK_SIZE) : // get size
return -1; // if you're unable to get the size
break;
}
// set new position in the file
return (int64_t)new_pos; // new position
}
将AVIOContext附加到AVFormatContext时,您还可以定义编解码器和探测器。这允许ffmpeg在流中寻找以更好地确定格式。
context->pb = ffio->avioContext;
context->flags = AVFMT_FLAG_CUSTOM_IO;
context->iformat = av_find_input_format("mpegts"); // not necessary
context->probesize = 1200000;
到目前为止,我还没有av_probe_input_format
的需要,但我的溪流再次出现了。
希望这有帮助。
编辑:在avio_alloc_context函数中添加了评论,以提及回调中ptr
的使用方式。
答案 1 :(得分:0)
虽然在您的情况下搜索是正确的答案,但事实是在我的情况下这是不可能的,因为我必须流式传输数据,在这种情况下搜索是不可能的。
所以我不得不调查:为什么需要搜索?
根据 ffmpeg 文档的说法,他们将缓存一些数据,以便在当前编码器/解码器需要时可以进行检索。但该缓冲区相对较小(您可能不想缓存 100 兆字节的数据)。
事实是 MP4 在文件末尾保存了一些元数据(一旦知道)。在读取该格式时,解码器想要寻找文件中非常远的位置(接近末尾)并读取所谓的 moov
原子。如果没有这些信息,系统就不想解压缩您的数据。
为了解决这个问题,我必须做的是使用以下命令移动 moov
原子:
ffmpeg -i far.mp4 -c copy -map 0 -movflags +faststart close.mp4
faststart
意味着您不必流式传输整个文件即可开始播放(解码)文件。