Hey guys!
I’m trying to transcode a video on my iOS app using FFMpeg/LibAv.
What I’m trying to accomplish is to transcode a video in order to resize
each frame and possibly lower the bitrate in order to save valuable MB in
the device.
The resulting video must be playable on all iPhone5+ devices.
After reading the documentation I found out that:
- I do not need to encode/decode the audio stream -> I’ll copy as-is to the
output file
- I need to encode the video using the h264 codec (LibX264) with a profile
supported by iOS:
- The baseline profile with level 3.0: Supported by all devices in the
iOS family (https://trac.ffmpeg.org/wiki/Encode/H.264#Compatibility)
- I’m also setting the picture format to YUV planar since it’s the only one
supported by iOS
- For the sake of testing I’m not using any filter (I’m using a
dummy/passthrough) at all or even trying to lower the bitrate, I’m just
trying to decode the video stream and encode it again
- Most of the code is based on the transcoding.c and filtering.c available
on the FFMpeg examples directory
FFMpeg-wise what I’m trying to achieve with LibAv is:
ffmpeg -i INPUT.MOV -c:v libx264 -preset ultrafast -profile:v baseline
-level 3.0
-c:a copy output.MOV
(the resulting file - which can be found below - is playable on QuickTime
if it’s generated by FFMpeg through the command line)
The original video was generated with a regular iPhone using iOS 8.2 but
the problem is not device specific or iOS specific, it occurs on all videos
generated with LibAv.
Although both resulting files are playable by VideoLan (VLC) the one I
generated through LibAv is not playable by QuickTime even though I can’t
find anything wrong with it.
As you can see below, I create the Video Streaming with the proper codec on
the call to avformat_new_stream:
- (int)openOutputFile:(const char *)filename {
AVStream *out_stream; // output stream
AVStream *in_stream; // input stream
AVCodecContext *dec_ctx, *enc_ctx; // codec context for the stream
AVCodec *encoder; // codec used
int ret;
unsigned int i;
ofmt_ctx = NULL;
// Allocate an AVFormatContext for an output format. This will be the
file header (similar to avformat_open_input but with an zero'ed memory)
avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, filename);
if (!ofmt_ctx) {
av_log(NULL, AV_LOG_ERROR, "Could not create output context\n");
[self errorWith:kErrorCreatingOutputContext and:@"Could not create
output context"];
return AVERROR_UNKNOWN;
}
// we must not use the AVCodecContext from the video stream directly!
So we have to use avcodec_copy_context() to copy the context to a new
location (after allocating memory for it, of course).
// iterate over all input streams
for (i = 0; i < ifmt_ctx->nb_streams; i++) {
in_stream = ifmt_ctx->streams[i]; // input stream
dec_ctx = in_stream->codec; // get the codec context for the decoder
if (dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO) {
// lets use h264
encoder = avcodec_find_encoder(AV_CODEC_ID_H264);
if (!encoder) {
[self errorWith:kErrorCodecNotFound and:@"H264 Codec Not
Found"];
return AVERROR_UNKNOWN;
}
out_stream = avformat_new_stream(ofmt_ctx, encoder); // create
a new stream with h264 codec
if (!out_stream) {
av_log(NULL, AV_LOG_ERROR, "Failed allocating output
stream\n");
[self errorWith:kErrorAllocateOutputStream and:@"Failed
allocating output stream"];
return AVERROR_UNKNOWN;
}
enc_ctx = out_stream->codec; // pointer to the stream codec
context
/* we transcode to same properties (picture size,
* sample rate etc.). These properties can be changed for output
* streams easily using filters */
if (dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO) {
enc_ctx->width = dec_ctx->width;
enc_ctx->height = dec_ctx->height;
enc_ctx->sample_aspect_ratio = dec_ctx->sample_aspect_ratio;
enc_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
enc_ctx->time_base = dec_ctx->time_base;
av_opt_set(enc_ctx->priv_data, "preset", "ultrafast", 0);
av_opt_set(enc_ctx->priv_data, "profile", "baseline", 0);
av_opt_set(enc_ctx->priv_data, "level", "3.0", 0);
}
out_stream->time_base = in_stream->time_base;
AVDictionaryEntry *tag = NULL;
while ((tag = av_dict_get(in_stream->metadata, "", tag,
AV_DICT_IGNORE_SUFFIX))) {
printf("%s=%s\n", tag->key, tag->value);
char *k = av_strdup(tag->key); // if your strings are
already allocated,
char *v = av_strdup(tag->value); // you can avoid
copying them like this
av_dict_set(&out_stream->metadata, k, v, 0);
}
ret = avcodec_open2(enc_ctx, encoder, NULL);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Cannot open video encoder for
stream #%u\n", i);
[self errorWith:kErrorCantOpenOutputFile and:[NSString
stringWithFormat:@"Cannot open video encoder for stream #%u",i]];
return ret;
}
}
else if(dec_ctx->codec_type == AVMEDIA_TYPE_UNKNOWN) {
// if we cant figure out the stream type, fail
av_log(NULL, AV_LOG_FATAL, "Elementary stream #%d is of unknown
type, cannot proceed\n", i);
[self errorWith:kErrorUnknownStream and:[NSString
stringWithFormat:@"Elementary stream #%d is of unknown type, cannot proceed"
,i]];
return AVERROR_INVALIDDATA;
}
else {
out_stream = avformat_new_stream(ofmt_ctx, NULL);
if (!out_stream) {
av_log(NULL, AV_LOG_ERROR, "Failed allocating output
stream\n");
[self errorWith:kErrorAllocateOutputStream and:@"Failed
allocating output stream"];
return AVERROR_UNKNOWN;
}
enc_ctx = out_stream->codec;
/* this stream must be remuxed */
// copies ifmt_ctx->streams[i]->codec into
ofmt_ctx->streams[i]->codec - Copy the settings of the source
AVCodecContext into the destination AVCodecContext.
ret = avcodec_copy_context(ofmt_ctx->streams[i]->codec,
ifmt_ctx->streams[i]->codec);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Copying stream context failed\n"
);
[self errorWith:kErrorCopyStreamFailed and:@"Copying stream
context failed"];
return ret;
}
}
// dunno what this is for
if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
enc_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
}
#ifdef ISDEV
av_dump_format(ofmt_ctx, 0, filename, 1);
#endif
if (!(ofmt_ctx->oformat->flags & AVFMT_NOFILE)) {
// Create and initialize a AVIOContext for accessing the
// resource indicated by url.
ret = avio_open(&ofmt_ctx->pb, filename, AVIO_FLAG_WRITE);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Could not open output file '%s'",
filename);
[self errorWith:kErrorCantOpenOutputFile and:[NSString
stringWithFormat:@"Could not open output file '%s'", filename]];
return ret;
}
}
/* init muxer, write output file header */
// Allocate the stream private data and write the stream header to an
output media file.
ret = avformat_write_header(ofmt_ctx, NULL);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Error occurred when opening output
file\n");
[self errorWith:kErrorOutFileCantWriteHeader and:@"Error occurred
when opening output file"];
return ret;
}
return 0;
}
I noticed the following output on xcode:
[libx264 @ 0x14601f200] MB rate (864000) > level limit (40500)
I don’t know what it means..
I also ran the command line ffprobe with the following arguments:
ffprobe -v error -show_format -show_streams file.MOV
There seem to be a few issues with frame rate/time:
FFMpeg version:
r_frame_rate=30/1
avg_frame_rate=30/1
time_base=1/15360
start_pts=0
start_time=0.000000
duration_ts=173056
duration=11.266667
LibAvFormat:
r_frame_rate=30/1
avg_frame_rate=9600/319
time_base=1/19200
start_pts=0
start_time=0.000000
duration_ts=214368
duration=11.165000
You can find the files here:
- Original final:
https://www.dropbox.com/s/2jjs1uy2pu2veyy/IMG_5705.MOV?dl=0
- File generated with FFMpeg -
https://www.dropbox.com/s/9hfmq3fcifgpfqc/local-ffmpeg.MOV?dl=0
- File generated by code -
https://www.dropbox.com/s/rttvny39rj7ejpf/generated-by-Ze.MOV?dl=0
Thank you so much,
Ze
_______________________________________________
libav-api mailing list
[email protected]
https://lists.libav.org/mailman/listinfo/libav-api