使用iOS将MOV转换为H.264 / MPEG-4吗?

时间:2019-05-24 14:23:09

标签: ios xamarin phasset avasset

在我们编写的应用程序中,用户需要能够选择和提交媒体附件,包括用她的设备录制的视频。然后,需要使用其他应用程序中的网络浏览器观看这些视频,因此这些视频必须为MP4(而非MOV)。

我第一次尝试转换剪辑使用PHImageManager.RequestExportSession,它可以工作,但是我们得到了H.265编码的MP4,许多浏览器仍然不支持,因此,我们需要H.264编码。我还没有找到一种配置导出会话以选择指定编码的方法,因此我改编了@SushiHangover here发布的代码,尝试了AVAsset方法。

我基于PHImageManager.RequestExportSession的转换如下:

// note: MediaBase is my own abstraction of media (AV and photos)
private void convertVideo(MediaBase mediaBase, MediaConversionOptions options, TaskCompletionSource<Uri> tcs)
{
    if (!(mediaBase.InternalObject is PHAsset asset))
        return;

    var videoOptions = makeVideoRequestOptions(options, out var exportPreset);
    if (options.ProgressHandler != null)
        videoOptions.ProgressHandler = onProgress;

    PHImageManager.DefaultManager.RequestExportSession(
        asset,
        videoOptions,
        exportPreset,
        (session, info) =>
        {
            session.OutputUrl = NSUrl.FromFilename(options.OutputPath ?? getDefaultOutputPath());
            session.OutputFileType = getFileTypeFrom(options.OutputType);
            session.ExportAsynchronously(() =>
            {
                switch (session.Status)
                {
                    case AVAssetExportSessionStatus.Failed:
                        var err = session.Error?.Description;
                        Crashes.TrackError(new Exception($"Could not convert movie to format: {options.OutputType}. {err}"));
                        tcs.SetResult(null);
                        break;

                    case AVAssetExportSessionStatus.Completed:
                        tcs.SetResult(new Uri(session.OutputUrl.AbsoluteString));
                        break;
                }
            });
        });

    void onProgress(double progress, NSError error, out bool stop, NSDictionary info)
    {
        Exception ex = error != null ? new Exception(error.Description) : null;
        Dictionary<string, object> dict = null; // consider supporting 'info' in Video conversion session
        options.ProgressHandler(progress, ex, out stop, dict);
    }

    string getDefaultOutputPath()
    {
        var outputFolder = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
        var file = $"{Guid.NewGuid().ToString("D")}{options.OutputType.GetFileExtension()}";
        return Path.Combine(outputFolder, file);
    }
}

...这是AVAsset的方法:

private void convertVideo2(MediaBase mediaBase, MediaConversionOptions options, string url, TaskCompletionSource<Uri> tcs)
{
    // adapted from SushiHangover's code:
    // https://stackoverflow.com/questions/38262302/xamarin-lowering-video-size

    if (!(mediaBase.InternalObject is PHAsset asset))
        return;

    try
    {
        var filename = new FileInfo(url).Name;
        var avAsset = AVAsset.FromUrl(new NSUrl(url));
        var reader = AVAssetReader.FromAsset(avAsset, out var assetreaderError); // todo Handle error
        var assetTrack = avAsset.Tracks.FirstOrDefault();
        if (assetTrack == null)
            return;

        Size dimensions = options.Dimensions ?? mediaBase.Dimensions;
        var inputSettings = new AVVideoSettingsUncompressed
        {
            Height = (int) dimensions.Width,
            Width = (int) dimensions.Height
        };
        var readerOutput = new AVAssetReaderTrackOutput(assetTrack, inputSettings)
        {
            AlwaysCopiesSampleData = false
        };
        var outFile = new FileInfo(options.OutputPath ?? Path.Combine(Path.GetTempPath(), $"_tmp_{filename}"));
        var extension = options.OutputType.GetFileExtension();
        outFile = outFile.ReplaceExtension(extension);
        if (outFile.Exists)
        {
            outFile.Delete();
        }
        var tempUrl = NSUrl.FromFilename(outFile.FullName);
        var writer = new AVAssetWriter(tempUrl, AVFileType.Mpeg4, out var assetWriterError);
        var outputSettings = new AVVideoSettingsCompressed
        {
            Height = (int)dimensions.Width,
            Width = (int)dimensions.Height,
            Codec = AVVideoCodec.H264, // todo honor Codec specified by options
            CodecSettings = new AVVideoCodecSettings { AverageBitRate = 1000000 } // todo honor bitrate specified by options
        };
        var writerInput = new AVAssetWriterInput(AVMediaType.Video, outputSettings)
        {
            ExpectsMediaDataInRealTime = false
        };
        writer.AddInput(writerInput);
        writer.StartWriting();
        reader.AddOutput(readerOutput);
        reader.StartReading();
        writer.StartSessionAtSourceTime(CoreMedia.CMTime.Zero);

        var queue = new DispatchQueue("mediaInputQueue");
        writerInput.RequestMediaData(queue, () =>
        {
            var isMoppedUp = false;
            while (writerInput.ReadyForMoreMediaData)
            {
                var nextBuffer = readerOutput.CopyNextSampleBuffer();
                if (nextBuffer != null)
                {
                    writerInput.AppendSampleBuffer(nextBuffer);
                    continue;
                }
                mopup(); // <-- never reached. Seems writerInput.ReadyForMoreMediaData gets flipped to false
                break;
            }

            mopup();
            tcs.SetResult(new Uri(outFile.FullName));

            void mopup()
            {
                if (isMoppedUp)
                    return;

                writerInput.MarkAsFinished();
                writer.FinishWritingAsync();
                reader.CancelReading();
                reader.Dispose();
                writer.Dispose();
                writerInput.Dispose();
            }

        });
    }
    catch (Exception ex)
    {
        System.Diagnostics.Debug.WriteLine($"Video conversion ERROR: {ex}");
    }
}

总结我的问题:

  1. 是否可以使用PHImageManager.RequestExportSession API指定首选编解码器?
  2. 对于AVAssetWriterInput.ReadyForMoreMediaData返回false显然会缩短转换时间的原因有何提示? (结果通常只有几帧)。

0 个答案:

没有答案