How to use ffmpeg faststart flag programmatically? - c++

I try to convert videos for playback on Android using H264, AAC codecs and mp4 container. Video plays normaly with non-system players. But system player shows error "Can't play this video".
I found out that the problem is in moov atom, which is writed in the end of the file.
When I use "-movflags +faststart" ffmeg flag to convert video, it plays normal, but when I try to do that programmatically, it gives no result. I use following code:
av_dict_set( &dict, "movflags", "faststart", 0 );
ret = avformat_write_header( ofmt_ctx, &dict );

This code works fine:
av_dict_set( &dict, "movflags", "faststart", 0 );
ret = avformat_write_header( ofmt_ctx, &dict );
But problem is not solved. I still can't play converted videos on Android devices.

I assume that this answer is very late, but still, for anyone who might still be facing the same issue: this might be caused by the AV_CODEC_FLAG_GLOBAL_HEADER not being set in the audio/video AVCodecContext. A lot of guides show that it needs to be set in the AVFormatContext, but it needs to be set in the AVCodecContext before opening it using avcodec_open2.
if (format_context->oformat->flags & AVFMT_GLOBALHEADER) {
video_codec_context->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
}
avcodec_open2(video_codec_context, video_codec, nullptr);

Maybe the video is not compatible with your android phone? Try to convert with h264 baseline profile.

TL;DR
Set url field of AVFormatContext before avformat_write_header.
Why
I hit same issue today, and I found there is a log when calling av_write_trailer:
Unable to re-open output file for the second pass (faststart)
In the movenc.c implementation, we can see it needs s->url to re-open the file:
avio_flush(s->pb);
ret = s->io_open(s, &read_pb, s->url, AVIO_FLAG_READ, NULL);
if (ret < 0) {
av_log(s, AV_LOG_ERROR, "Unable to re-open %s output file for "
"the second pass (faststart)\n", s->url);
goto end;
}

Related

FFmpeg API example (encode_video.c) does not work correctly

I am using the official encode_video.c example to test if FFmpeg works correctly for me.
I got the pre-built windows edition from ffmpeg.zeranoe.com/builds. It is built already with libx264 and other external libraries. I got both dev and shared editions and added the DLLs, header files and libs accordingly in Visual Studio.
Now the encode_video.c example does not work correctly.
What I tried:
I compiled the example and run it on many different file formats and codecs such as the following.
First I tried all of these file formats (.mp4, .m4v, .h264, .x264, .avi, .flv) with codec name as libx264. The code executed without errors but the output video file did not play in VLC or Windows 10 default player.
Next, I tried all of those above file formats but with codec name as mpeg4. The code executed without errors but the output video file played only for .m4v in VLC.
What is expected:
All of those combinations should have produced a video file which could be played in VLC. None of them worked except for .m4v as file format and mpeg4 as codec name.
Please tell me how to make this work for h264. I mainly want it to work for h264 as that is only important for now.
I am running the code like ./encode_video.exe test.mp4 libx264 where first argument is output filename and second argument is codec name.
This is the output for test.mp4 and libx264 as command line arguments https://imgur.com/a/AHLQwuK
It seems that in the encode function, it goes over the below code and returns because of AVERROR(EAGAIN) or AVERROR_EOF. Please tell me what is happening.
while (ret >= 0) {
ret = avcodec_receive_packet(enc_ctx, pkt);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
return;
else if (ret < 0) {
fprintf(stderr, "Error during encoding\n");
exit(1);
}
printf("Write packet %3"PRId64" (size=%5d)\n", pkt->pts, pkt->size);
fwrite(pkt->data, 1, pkt->size, outfile);
av_packet_unref(pkt);
}
I used DepenciesGUI to find out the DLLs linked and it shows that the DLLs are correctly linked. Please help me figure out what the problem is now!!
I found this question because I had the same issue (couldn't watch video generated by that example). Regarding your concern:
It seems that in the encode function, it goes over the below code and
returns because of AVERROR(EAGAIN) or AVERROR_EOF. Please tell me what
is happening.
While stepping through the code in debugger, I too noticed those occasional "errors". However, those frames are eventually processed either in subsequent loop iteration or when the encoder is flashed via:
/* flush the encoder */
encode(c, NULL, pkt, f);
I too tried to use VLC, and also QuickTime, with no luck.
Then I noticed ffplay tool in the ffmpeg's bin folder, that would play all videos produced by that example, with different codecs.
My point is - the issue might be with the viewer, not with the video file.
just change:
./encode_video.exe test.mp4 libx264
to:
./encode_video.exe test.264 libx264
and the result file "test.264" can be played by vlc player.
I'm running ffmpeg's encode_video.c demo on my Mac and Apple's QuickTime Player seems not supporting this format.
You want to use the muxing.c example instead.
The encode_video example doesn't produce an MPEG compliant file.

Media Foundation encode AVI Raw index error

I am trying to encode a video using MediaFoundation on Windows 8.1, the output file should be an avi container, with a raw stream NV12.
The video is generated, and I can play it but when I open it with VLC I get the broken index detected (With options to play as is or fix it temporarly). If I press play as is, it plays ok. But this is generating a problem in other softwares.
I tried to research this and noticed that AVI supports 2 types of indexing
https://msdn.microsoft.com/en-us/library/windows/desktop/dd318189(v=vs.85).aspx
But this is for DirectShow and I am using MediaFoundation and I can't find anything related to this inside MediaFoundation.
Any help or suggestion will be appreciated.
Thanks.
Edit:
This is how I am creating the IMFMediaSink:
hr = MFCreateAttributes(&sinkAttributes, 5);
assert(SUCCEEDED(hr));
sinkAttributes->SetUnknown(MF_SINK_WRITER_ASYNC_CALLBACK, this);
sinkAttributes->SetGUID(MF_TRANSCODE_CONTAINERTYPE, MFTranscodeContainerType_AVI);
if (SUCCEEDED(hr)){
hr = MFCreateSinkWriterFromURL(videoURL, NULL, sinkAttributes, (IMFSinkWriter**)&sinkWriter);
}

C++ FFmpeg pick codec from system

I'm currently setting up my output context for creating .avi like this:
avformat_alloc_output_context2(&outContext, NULL, NULL, "out.avi");
if (!outContext)
die("Could not allocate output context");
However, the resulting video quality is very unpleasant. As such, I'd like to be able to fetch the installed codecs on the system and use one of them in avformat_alloc_output_context2. Similar to below:
So I guess my two questions are:
How do I create a list (array) containing the installed codecs (as above)?
How do I use one of them in the output container?
If possible, I'd also like to be able to modify output quality (0%-100%) and open the codec configuration window.
First, make your map with string(or whatever) with AVCodecID, like this :
std::map<string, AVCodecID> _codecList;
_codecList["h264"] = AV_CODEC_ID_H264;
_codecList["mpeg4"] = AV_CODEC_ID_MPEG4;
....
Note that FFmpeg does not provide information that which codec is available in what container so you should validate yourself. but you can reference following link(at least it is officlal) : https://en.wikipedia.org/wiki/Comparison_of_video_container_formats
Next thing to do is that find encoder by name, or AVCodecID in following code :
avcodec_find_encoder_by_name("libx264");
avcodec_find_encoder(AV_CODEC_ID_H264);
Both are return AVCodec* so you can use this when calling avformat_new_stream(), like this :
AVCodecID codec_id = (_codecList.find("h264") != _codecList.end()) ?
_codecList.find("h264") : AV_CODEC_ID_NONE;
if(codec_id == AV_CODEC_ID_NONE)
{
return -1;
}
AVCodec* encoder = avcodec_find_encoder(codec_id);
// or you can just get it from avcodec_find_encoder_by_name("libx264");
AVStream* newStream = avformat_new_stream(avFormatContext, encoder);
Thre are so many things when determining video quality. x264, especially has more options. In this case, you can list it by crf value or bitrate things(you can't use both option). You can determine it with AVCodecContext.
AVCodecContex* codec_ctx = newStream->codec;
codec_ctx->bitrate = 1000000 // 1MB
// codec_ctx->qmin = 18;
// codec_ctx->qmin = 31;
Once you done, open it with avcodec_open2
avcodec_open2(avFormatContext, encoder, NULL);
And Don't forget to close when you release it.
avcodec_close(avFormatContext);
There is much to do when you creating your own output stream. If you have deeper experience with it, i think that this answer will be enough.
But If you don't have much experience with FFmpeg, you can find my full example in here(https://github.com/sorrowhill/FFmpegTutorial)

How to set the moov atom position when encoding video using ffmpeg in c++

I'm encoding some h264 video into a mp4 container using ffmpeg in c++. But the result videos place the moov atom(or metadata?) at the end of the video file, it's bad for internet streaming.
So how can I set the moov atom position to the front?
MOVMuxContext is an internal header and should not be accessed directly. Its implementation is not part of the API and it can change.
The official way to do it is setting options via an AVDictionary :
AVDictionary* options = nullptr;
av_dict_set( &options, "movflags", "faststart", 0 );
avio_open2(..., &options);
You need to use faststart flag of ffmpeg to place the moov atom in the beginning of the MP4 file, Here is the explanation of the flag. Programatically you need to set the flag in output context, here is the sample code and its working for me,
AVFormatContext *outFormatCtx;
// Write MOOV atom at the begining of the MP4 file
MOVMuxContext *mov = NULL;
mov = (MOVMuxContext *)outFormatCtx->priv_data;
mov->flags |= FF_MOV_FLAG_FASTSTART;

Problem with ffmpeg function avformat_seek_file()

I am trying to seek the given frame in the video using ffmpeg library. I knew that there is av_seek_frame() function but it was recommended to use avformat_seek_file()instead. Something similar mentioned here.
I know that avformat_seek_file() can't always take you to exact frame you want, but this is ok for me. I just want to jump to the nearest keyframe. So i open the video, find videostream and calling it like this:
avformat_seek_file( formatContext, streamId, 0, frameNumber, frameNumber, AVSEEK_FLAG_FRAME )
It always returns 0, so i understand it as correct finish. However, it doesn't work as it should to. I check byte position like here before and after calling avformat_seek_file(). Actually it changes, but it changes always in the same way whenever i'm trying to put different target frame numbers! I mean that byteposition after this call is always same even with different frameNumber values. Obviously, i'm doing something wrong but i don't know what exactly. I don't know if it does matter but i'm using .h264 files for that. I tried different flags, different files, using timestamps instead of frames, flushing buffers before and after and so on but it doesn't work for me. I will be very grateful if someone could show me what is wrong with it.
I had the same issue, see the code bellow (it works for me):
...
checkPosition(input_files[file_index].ctx);
...
void checkPosition(AVFormatContext *is) {
int stream_index = av_find_default_stream_index(is);
//Convert ts to frame
tm = av_rescale(tm, is->streams[stream_index]->time_base.den, is->streams[stream_index]->time_base.num);
tm /= 1000;
//SEEK
if (avformat_seek_file(is, stream_index, INT64_MIN, tm, INT64_MAX, 0) < 0) {
av_log(NULL, AV_LOG_ERROR, "ERROR av_seek_frame: %u\n", tm);
} else {
av_log(NULL, AV_LOG_ERROR, "SUCCEEDED av_seek_frame: %u newPos:%d\n", tm, is->pb->pos);
avcodec_flush_buffers(is->streams[stream_index]->codec);
}
}
your problem may be related to the fact that your input is raw .h264. try using e.g. mp4box to mux it into a .mp4 file, then load the mp4 file with ffmpeg and try to seek to a keyframe again. e.g.:
mp4box -new -add my_file.h264 my_file.mp4