Преобразование MOV в H.264 / MPEG-4 с iOS? - PullRequest
0 голосов
/ 24 мая 2019

В приложении, которое мы пишем, пользователь должен иметь возможность выбирать и отправлять мультимедийные вложения, в том числе видео, записанное на его устройстве.Затем эти видео необходимо просматривать с помощью веб-браузеров в других приложениях и, следовательно, должно быть MP4 (не MOV).

Моя первая попытка конвертировать клипы использует PHImageManager.RequestExportSession, и она работает, но мы получаем MP4 в кодировке H.265, который все еще не поддерживается многими браузерами, и поэтому вместо этого нам нужно кодирование H.264.Я не нашел способа настроить сеанс экспорта для выбора указанной кодировки, поэтому я вместо этого попробовал подход AVAsset, адаптировав код, размещенный @SushiHangover здесь .

Моя 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, видимо, сокращает конверсию?(результат обычно составляет всего несколько кадров).
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...