使用 LibAvFormat 转码视频以便在 iOS 设备上播放

use*_*526 5 ffmpeg h.264 libavcodec libav libavformat

我\xe2\x80\x99m 尝试使用 FFMpeg/LibAv 在我的 iOS 应用程序上对视频进行转码。\n我\xe2\x80\x99m 试图完成的是对视频进行转码,以便调整每个帧的大小并可能降低比特率为了在设备中节省宝贵的 MB。

\n\n

生成的视频必须可以在所有 iPhone5+ 设备上播放。

\n\n

阅读文档后我发现:

\n\n
    \n
  • 我不需要对音频流进行编码/解码 -> I\xe2\x80\x99ll 按原样复制到输出文件
  • \n
  • 我需要使用 h264 编解码器 (LibX264) 和 iOS 支持的配置文件(级别为 3.0 的基线配置文件 - https://trac.ffmpeg.org/wiki/Encode/H.264#Compatibility)对视频进行编码
  • \n
  • 我\xe2\x80\x99m还将图片格式设置为YUV平面,因为它\xe2\x80\x99s是iOS唯一支持的一种
  • \n
  • 为了测试 I\xe2\x80\x99m 根本不使用任何过滤器(I\xe2\x80\x99m 使用虚拟/直通),甚至尝试降低比特率,I\xe2\x80\x99m 只是尝试解码视频流并再次编码
  • \n
  • 大部分代码基于 FFMpeg 示例目录中的 transcoding.c 和filtering.c
  • \n
\n\n

从 FFMpeg 角度来看,我尝试使用 LibAv 实现的目标是:

\n\n
ffmpeg -i INPUT.MOV -c:v libx264 -preset ultrafast -profile:v baseline -level 3.0  -c:a copy output.MOV\n
Run Code Online (Sandbox Code Playgroud)\n\n

(生成的文件 - 可以在下面找到 - 如果它是由 FFMpeg 通过命令行生成的\xe2\x80\x99s,则可以在 QuickTime 上播放)

\n\n

原始视频是使用 iOS 8.2 使用普通 iPhone 生成的,但问题不是特定于设备或 iOS 特定的,它出现在使用 LibAv 生成的所有视频上。

\n\n

虽然这两个生成的文件都可以通过 VideoLan (VLC) 播放,但我通过 LibAv 生成的文件不能通过 QuickTime 播放,尽管我无法\xe2\x80\x99 发现它有任何问题。

\n\n

如下所示,我在调用 avformat_new_stream 时使用正确的视频编解码器创建视频流:

\n\n
AVStream *out_stream; // output stream\nAVStream *in_stream; // input stream\nAVCodecContext *dec_ctx, *enc_ctx; // codec context for the stream\nAVCodec *encoder; // codec used\nint ret;\nunsigned int i;\n\nofmt_ctx = NULL;\n// Allocate an AVFormatContext for an output format. This will be the file header (similar to avformat_open_input but with an zero\'ed memory)\navformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, filename);\nif (!ofmt_ctx) {\n    av_log(NULL, AV_LOG_ERROR, "Could not create output context\\n");\n    [self errorWith:kErrorCreatingOutputContext and:@"Could not create output context"];\n    return AVERROR_UNKNOWN;\n}\n// 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).\n// iterate over all input streams\nfor (i = 0; i < ifmt_ctx->nb_streams; i++) {\n    in_stream = ifmt_ctx->streams[i]; // input stream\n    dec_ctx = in_stream->codec; // get the codec context for the decoder\n\n    if (dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO) {\n        // lets use h264\n        encoder = avcodec_find_encoder(AV_CODEC_ID_H264);\n        if (!encoder) {\n            [self errorWith:kErrorCodecNotFound and:@"H264 Codec Not Found"];\n            return AVERROR_UNKNOWN;\n        }\n        out_stream = avformat_new_stream(ofmt_ctx, encoder); // create a new stream with h264 codec\n        if (!out_stream) {\n            av_log(NULL, AV_LOG_ERROR, "Failed allocating output stream\\n");\n            [self errorWith:kErrorAllocateOutputStream and:@"Failed allocating output stream"];\n            return AVERROR_UNKNOWN;\n        }\n        enc_ctx = out_stream->codec; // pointer to the stream codec context\n        /* we transcode to same properties (picture size,\n         * sample rate etc.). These properties can be changed for output\n         * streams easily using filters */\n        if (dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO) {\n            enc_ctx->width = dec_ctx->width;\n            enc_ctx->height = dec_ctx->height;\n            enc_ctx->sample_aspect_ratio = dec_ctx->sample_aspect_ratio;\n            enc_ctx->pix_fmt = AV_PIX_FMT_YUV420P;\n            enc_ctx->time_base = dec_ctx->time_base;\n            av_opt_set(enc_ctx->priv_data, "preset", "ultrafast", 0);\n            av_opt_set(enc_ctx->priv_data, "profile", "baseline", 0);\n            av_opt_set(enc_ctx->priv_data, "level", "3.0", 0);\n        }\n\n        out_stream->time_base = in_stream->time_base;\n\n        AVDictionaryEntry *tag = NULL;\n        while ((tag = av_dict_get(in_stream->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) {\n            printf("%s=%s\\n", tag->key, tag->value);\n            char *k = av_strdup(tag->key);       // if your strings are already allocated,\n            char *v = av_strdup(tag->value);     // you can avoid copying them like this\n            av_dict_set(&out_stream->metadata, k, v, 0);\n        }\n\n        ret = avcodec_open2(enc_ctx, encoder, NULL);\n        if (ret < 0) {\n            av_log(NULL, AV_LOG_ERROR, "Cannot open video encoder for stream #%u\\n", i);\n            [self errorWith:kErrorCantOpenOutputFile and:[NSString stringWithFormat:@"Cannot open video encoder for stream #%u",i]];\n            return ret;\n        }\n\n    }\n    else if(dec_ctx->codec_type == AVMEDIA_TYPE_UNKNOWN) {\n        // if we cant figure out the stream type, fail\n        av_log(NULL, AV_LOG_FATAL, "Elementary stream #%d is of unknown type, cannot proceed\\n", i);\n        [self errorWith:kErrorUnknownStream and:[NSString stringWithFormat:@"Elementary stream #%d is of unknown type, cannot proceed",i]];\n        return AVERROR_INVALIDDATA;\n    }\n    else {\n        out_stream = avformat_new_stream(ofmt_ctx, NULL);\n        if (!out_stream) {\n            av_log(NULL, AV_LOG_ERROR, "Failed allocating output stream\\n");\n            [self errorWith:kErrorAllocateOutputStream and:@"Failed allocating output stream"];\n            return AVERROR_UNKNOWN;\n        }\n        enc_ctx = out_stream->codec;\n        /* this stream must be remuxed */\n        // copies ifmt_ctx->streams[i]->codec into ofmt_ctx->streams[i]->codec - Copy the settings of the source AVCodecContext into the destination AVCodecContext.\n        ret = avcodec_copy_context(ofmt_ctx->streams[i]->codec,\n                                   ifmt_ctx->streams[i]->codec);\n        if (ret < 0) {\n            av_log(NULL, AV_LOG_ERROR, "Copying stream context failed\\n");\n            [self errorWith:kErrorCopyStreamFailed and:@"Copying stream context failed"];\n            return ret;\n        }\n\n    }\n    // dunno what this is for\n    if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)\n        enc_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;\n\n}\nif (!(ofmt_ctx->oformat->flags & AVFMT_NOFILE)) {\n    // Create and initialize a AVIOContext for accessing the\n    // resource indicated by url.\n    ret = avio_open(&ofmt_ctx->pb, filename, AVIO_FLAG_WRITE);\n    if (ret < 0) {\n        av_log(NULL, AV_LOG_ERROR, "Could not open output file \'%s\'", filename);\n        [self errorWith:kErrorCantOpenOutputFile and:[NSString stringWithFormat:@"Could not open output file \'%s\'", filename]];\n        return ret;\n    }\n}\n\n/* init muxer, write output file header */\n// Allocate the stream private data and write the stream header to an output media file.\nret = avformat_write_header(ofmt_ctx, NULL);\nif (ret < 0) {\n    av_log(NULL, AV_LOG_ERROR, "Error occurred when opening output file\\n");\n    [self errorWith:kErrorOutFileCantWriteHeader and:@"Error occurred when opening output file"];\n    return ret;\n}\nreturn 0;\n
Run Code Online (Sandbox Code Playgroud)\n\n

您可以在此处找到这些文件:

\n\n
    \n
  1. 原决赛:https://www.dropbox.com/s/2jjs1uy2pu2veyy/IMG_5705.MOV? dl=0
  2. \n
  3. 使用 FFMpeg 生成的文件 - https://www.dropbox.com/s/9hfmq3fcifgpfqc/local-ffmpeg.MOV?dl=0
  4. \n
  5. 由代码生成的文件 - https://www.dropbox.com/s/rttvny39rj7ejpf/ generated-by-Ze.MOV?dl=0
  6. \n
\n\n

非常感谢,\nZe

\n