问题描述
我需要使用ffmpeg的c API将原始帧编码为mkv容器内的h264流。
所有示例我都可以从解码器中复制现有的编解码器参数,因此我必须对其进行一些修改。
I need to encode raw frames into an h264 stream inside an mkv container using ffmpeg's c api.All the examples I could find copy existing codec parameters from a decoder so I had to modify it a bit.
#include <cstdio>
#include <cstring>
#include <iostream>
extern "C" {
#include <libavformat/avformat.h>
#include <libavutil/avutil.h>
#include <libavutil/imgutils.h>
#include <libavutil/opt.h>
#include <libswscale/swscale.h>
}
static char error_msg[4096] = "";
int main(int argc, char **argv) {
int err;
char *filename = (char *)av_malloc(4096);
AVCodec *codec = nullptr;
SwsContext *color_converter = nullptr;
AVCodecContext *encoder = nullptr;
AVFormatContext *container = nullptr;
AVStream *stream = nullptr;
std::snprintf(filename, sizeof(filename), "%s", "out.mkv");
// Create encoder
codec = avcodec_find_encoder_by_name("libx264");
if (codec == NULL) {
std::cerr << "codec not found" << std::endl;
goto cleanup;
}
encoder = avcodec_alloc_context3(codec);
if (encoder == nullptr) {
std::cerr << "failed to allocate encoder" << std::endl;
goto cleanup;
}
// Configure encoder
encoder->color_range = AVCOL_RANGE_JPEG;
encoder->height = 1024;
encoder->width = 1280;
encoder->sample_aspect_ratio = {1, 1};
encoder->pix_fmt = AV_PIX_FMT_YUVJ420P;
encoder->time_base = {1, 10};
encoder->framerate = {10, 1};
err = av_opt_set_double(encoder->priv_data, "crf", 0.0, 0);
if (err < 0) {
av_make_error_string(error_msg, sizeof(error_msg), err);
std::cerr << "failed to initialize encoder: " << error_msg << std::endl;
goto cleanup;
}
err = avcodec_open2(encoder, codec, nullptr);
if (err < 0) {
av_make_error_string(error_msg, sizeof(error_msg), err);
std::cerr << "failed to initialize encoder: " << error_msg << std::endl;
goto cleanup;
}
// Create container
avformat_alloc_output_context2(&container, NULL, NULL, filename);
if (container == NULL) {
std::cerr << "failed to allocate container" << std::endl;
goto cleanup;
}
// Add stream
stream = avformat_new_stream(container, nullptr);
if (stream == NULL) {
std::cerr << "failed to create new stream" << std::endl;
goto cleanup;
}
stream->time_base = encoder->time_base;
err = avcodec_parameters_from_context(stream->codecpar, encoder);
if (err < 0) {
av_make_error_string(error_msg, sizeof(error_msg), err);
std::cerr << "failed to configure stream: " << error_msg << std::endl;
goto cleanup;
}
// encoder->extradata_size == 0 at this point!
// Open file and write file header
err = avio_open(&container->pb, filename, AVIO_FLAG_WRITE);
if (err < 0) {
av_make_error_string(error_msg, sizeof(error_msg), err);
std::cerr << "failed to open output file: " << error_msg << std::endl;
goto cleanup;
}
err = avformat_write_header(container, nullptr);
// fails in ff_isom_write_avcc because stream->codecpar->extradata is null.
if (err < 0) {
av_make_error_string(error_msg, sizeof(error_msg), err);
std::cerr << "failed to set-up container: " << error_msg << std::endl;
goto cleanup;
}
cleanup:
avcodec_close(encoder);
if (container != nullptr) {
avio_closep(&container->pb);
}
avcodec_free_context(&encoder);
sws_freeContext(color_converter);
avformat_free_context(container);
av_free(filename);
return 0;
}
此操作在avformat_write_header行出现EINVAL(处理输入时发现无效数据)而失败(确切地说是在ffmpeg库中的ff_isom_write_avcc中),因为stream-> codecpar-> extradata为null。相同的代码适用于mp4容器,但我不知道如何手动初始化stream-> codecpar->额外数据(或编码器->额外数据)。
This fails with EINVAL (Invalid data found when processing input) at the avformat_write_header line (more precisely in ff_isom_write_avcc inside the ffmpeg library) because stream->codecpar->extradata is null. The same code works for an mp4 container and I have no clue how to initialize stream->codecpar->extradata (or encoder->extradata) manually.
我检查了关于该问题的类似问题,但找不到最终答案:
I have checked similar questions about that problem but could not find a conclusive answer:
- 说,当h264采用附件b格式时,Extradata可以为空
- 说它不应为空。
- https://stackoverflow.com/a/22231863/5786475 says extradata can be null when h264 is in annex b format
- https://stackoverflow.com/a/27471912/5786475 says it shouldn't be null.
推荐答案
在打开之前,将AV_CODEC_FLAG_GLOBAL_HEADER设置为编码器->标志。这将告诉x264将额外的数据添加到编码器上下文中,而不是在编码的位流中将其作为带内发送。 Matroska多路复用器仅查看此额外数据的AVStream编解码器参数,并从编码器上下文而不是代码中的任何数据包复制到那里。
Set AV_CODEC_FLAG_GLOBAL_HEADER for encoder->flags before opening. This will tell x264 to add extradata to the encoder context instead of sending it as in-band within the encoded bitstream. The Matroska muxer only looks at AVStream codec parameters for this extradata and it is copied there from encoder context and not any packets in your code.
这篇关于使用h264流创建mkv容器时,数据无效,因为extradata为null的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!