/* * Copyright (c) 2016 Samsung Electronics Co., Ltd All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the License); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an AS IS BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using System; using System.Collections.Generic; using System.Diagnostics; namespace Tizen.Multimedia.MediaCodec { /// /// Provides the means to encode and decode video and audio data. /// public class MediaCodec : IDisposable { private const int CodecTypeMask = 0xFFFF; private const int CodecKindMask = 0x3000; // private const int CodecKindAudio = 0x1000; // Not used private const int CodecKindVideo = 0x2000; private IntPtr _handle; /// /// Initialize a new instance of the MediaCodec class. /// public MediaCodec() { int ret = Interop.MediaCodec.Create(out _handle); if (ret == (int)MediaCodecErrorCode.InvalidOperation) { throw new InvalidOperationException("Not able to initialize a new media codec."); } MultimediaDebug.AssertNoError(ret); RegisterInputProcessed(); RegisterErrorOccurred(); } #region IDisposable-support private bool _isDisposed = false; protected virtual void Dispose(bool disposing) { if (!_isDisposed) { if (_handle != IntPtr.Zero) { Interop.MediaCodec.Destroy(_handle); _handle = IntPtr.Zero; } _isDisposed = true; } } ~MediaCodec() { Dispose(false); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } #endregion /// /// Validates if the object already has been disposed of. /// /// The current object has been disposed of. private void ValidateNotDisposed() { if (_isDisposed) { throw new ObjectDisposedException(nameof(MediaCodec)); } } private static IEnumerable _supportedVideoCodecs; /// /// Gets the audio codec list that the current device supports. /// public static IEnumerable SupportedVideoCodecs { get { if (_supportedVideoCodecs == null) { LoadSupportedCodec(); } return _supportedVideoCodecs; } } private static IEnumerable _supportedAudioCodecs; /// /// Gets the audio codec list that the current device supports. /// public static IEnumerable SupportedAudioCodecs { get { if (_supportedAudioCodecs == null) { LoadSupportedCodec(); } return _supportedAudioCodecs; } } private static bool TryGetMimeTypeFromCodecType(int codecType, ref T result) { if (codecType == -1) { return false; } foreach (T value in Enum.GetValues(typeof(T))) { if ((Convert.ToInt32(value) & CodecTypeMask) == codecType) { result = value; return true; } } Debug.Fail($"Unknown codec : { codecType }."); return false; } private static void LoadSupportedCodec() { var videoCodecList = new List(); var audioCodecList = new List(); Interop.MediaCodec.SupportedCodecCallback cb = (codecType, _) => { if ((codecType & CodecKindMask) == CodecKindVideo) { MediaFormatVideoMimeType mimeType = 0; if (TryGetMimeTypeFromCodecType(codecType, ref mimeType)) { videoCodecList.Add(mimeType); } } else { MediaFormatAudioMimeType mimeType = 0; if (TryGetMimeTypeFromCodecType(codecType, ref mimeType)) { audioCodecList.Add(mimeType); } } return true; }; int ret = Interop.MediaCodec.ForeachSupportedCodec(cb, IntPtr.Zero); MultimediaDebug.AssertNoError(ret); _supportedVideoCodecs = videoCodecList.AsReadOnly(); _supportedAudioCodecs = audioCodecList.AsReadOnly(); } /// /// Prepares the MediaCodec for encoding or decoding. /// /// /// The codec is not configured, yet.\n /// -or-\n /// Internal error. /// public void Prepare() { ValidateNotDisposed(); int ret = Interop.MediaCodec.Prepare(_handle); if (ret == (int)MediaCodecErrorCode.NotInitialized) { throw new InvalidOperationException("The codec is not configured."); } if (ret != (int)MediaCodecErrorCode.None) { throw new InvalidOperationException("Operation failed."); } MultimediaDebug.AssertNoError(ret); } /// /// Unprepares the MediaCodec. /// public void Unprepare() { ValidateNotDisposed(); int ret = Interop.MediaCodec.Unprepare(_handle); MultimediaDebug.AssertNoError(ret); } /// /// Configures the MediaCodec. /// /// The for properties of media data to decode or encode. /// The value indicating whether the codec works as a encoder or a decoder. /// The value indicating whether the codec uses hardware acceleration. /// format is null /// /// is invalid.\n /// -or-\n /// is neither audio type nor video type. /// /// the mime type of the format is not supported. /// /// public void Configure(MediaFormat format, bool encoder, MediaCodecTypes codecType) { ValidateNotDisposed(); if (format == null) { throw new ArgumentNullException(nameof(format)); } if (codecType != MediaCodecTypes.Hardware && codecType != MediaCodecTypes.Software) { throw new ArgumentException("codecType is invalid."); } if (format.Type == MediaFormatType.Audio) { ConfigureAudio((AudioMediaFormat)format, encoder, codecType); } else if (format.Type == MediaFormatType.Video) { ConfigureVideo((VideoMediaFormat)format, encoder, codecType); } else { throw new ArgumentException("Only video and audio formats are allowed."); } } private void ConfigureAudio(AudioMediaFormat format, bool encoder, MediaCodecTypes supportType) { int codecType = (int)format.MimeType & CodecTypeMask; if (!Enum.IsDefined(typeof(SupportedCodecType), codecType)) { throw new NotSupportedException("The format is not supported " + $"mime type : { Enum.GetName(typeof(MediaFormatAudioMimeType), format.MimeType) }"); } DoConfigure(codecType, encoder, supportType); if (encoder) { int ret = Interop.MediaCodec.SetAudioEncoderInfo(_handle, format.SampleRate, format.Channel, format.Bit, format.BitRate); MultimediaDebug.AssertNoError(ret); } else { int ret = Interop.MediaCodec.SetAudioDecoderInfo(_handle, format.SampleRate, format.Channel, format.Bit); MultimediaDebug.AssertNoError(ret); } } private void ConfigureVideo(VideoMediaFormat format, bool encoder, MediaCodecTypes supportType) { int codecType = (int)format.MimeType & CodecTypeMask; if (!Enum.IsDefined(typeof(SupportedCodecType), codecType)) { throw new NotSupportedException("The format is not supported." + $"mime type : { Enum.GetName(typeof(MediaFormatVideoMimeType), format.MimeType) }"); } DoConfigure(codecType, encoder, supportType); if (encoder) { int ret = Interop.MediaCodec.SetVideoEncoderInfo(_handle, format.Size.Width, format.Size.Height, format.FrameRate, format.BitRate / 1000); MultimediaDebug.AssertNoError(ret); } else { int ret = Interop.MediaCodec.SetVideoDecoderInfo(_handle, format.Size.Width, format.Size.Height); MultimediaDebug.AssertNoError(ret); } } private void DoConfigure(int codecType, bool encoder, MediaCodecTypes supportType) { Debug.Assert(Enum.IsDefined(typeof(SupportedCodecType), codecType)); int flags = (int)(encoder ? MediaCodecCodingType.Encoder : MediaCodecCodingType.Decoder); flags |= (int)supportType; int ret = Interop.MediaCodec.Configure(_handle, codecType, flags); if (ret == (int)MediaCodecErrorCode.NotSupportedOnDevice) { throw new NotSupportedException("The format is not supported."); } MultimediaDebug.AssertNoError(ret); } /// /// Adds the packet to the internal queue of the codec. /// /// The packet to be encoded or decoded. /// packet is null. /// the current codec is not prepared, yet. /// Any attempts to modify the packet will be failed until the event for the packet is invoked. public void ProcessInput(MediaPacket packet) { ValidateNotDisposed(); if (packet == null) { throw new ArgumentNullException(nameof(packet)); } MediaPacket.Lock packetLock = MediaPacket.Lock.Get(packet); int ret = Interop.MediaCodec.Process(_handle, packetLock.GetHandle(), 0); if (ret == (int)MediaCodecErrorCode.InvalidState) { throw new InvalidOperationException("The codec is in invalid state."); } MultimediaDebug.AssertNoError(ret); } /// /// Flushes both input and output buffers. /// public void FlushBuffers() { ValidateNotDisposed(); int ret = Interop.MediaCodec.FlushBuffers(_handle); MultimediaDebug.AssertNoError(ret); } /// /// Retrieves supported codec types for the specified params. /// /// The value indicating encoder or decoder. /// The mime type to query. /// The values indicating which codec types are supported on the current device. /// type is invalid. public MediaCodecTypes GetCodecType(bool encoder, MediaFormatVideoMimeType type) { ValidateNotDisposed(); if (CheckMimeType(typeof(MediaFormatVideoMimeType), (int)type) == false) { return 0; } return GetCodecType((int)type, encoder); } /// /// Retrieves supported codec types for the specified params. /// /// The value indicating encoder or decoder. /// The mime type to query. /// The values indicating which codec types are supported on the current device. /// type is invalid. public MediaCodecTypes GetCodecType(bool encoder, MediaFormatAudioMimeType type) { ValidateNotDisposed(); if (CheckMimeType(typeof(MediaFormatAudioMimeType), (int)type) == false) { return 0; } return GetCodecType((int)type, encoder); } private MediaCodecTypes GetCodecType(int mimeType, bool isEncoder) { int codecType = mimeType & CodecTypeMask; int value = 0; int ret = Interop.MediaCodec.GetSupportedType(_handle, codecType, isEncoder, out value); MultimediaDebug.AssertNoError(ret); return (MediaCodecTypes)value; } private bool CheckMimeType(Type type, int value) { int codecType = value & CodecTypeMask; if (!Enum.IsDefined(type, value)) { throw new ArgumentException($"The mime type value is invalid : { value }."); } return Enum.IsDefined(typeof(SupportedCodecType), codecType); } #region OutputAvailable event private EventHandler _outputAvailable; private Interop.MediaCodec.OutputBufferAvailableCallback _outputBufferAvailableCb; /// /// Occurs when an output buffer is available. /// /// The output packet needs to be disposed after it is used to clean up unmanaged resources. public event EventHandler OutputAvailable { add { ValidateNotDisposed(); if (_outputAvailable == null) { RegisterOutputAvailableCallback(); } _outputAvailable += value; } remove { ValidateNotDisposed(); _outputAvailable -= value; if (_outputAvailable == null) { UnregisterOutputAvailableCallback(); } } } private void RegisterOutputAvailableCallback() { _outputBufferAvailableCb = (packetHandle, _) => { OutputAvailableEventArgs args = null; try { args = new OutputAvailableEventArgs(packetHandle); } catch (Exception) { Interop.MediaPacket.Destroy(packetHandle); // TODO should we throw it to unmanaged code? throw; } //TODO dispose if no event handler registered _outputAvailable?.Invoke(this, args); }; int ret = Interop.MediaCodec.SetOutputBufferAvailableCb(_handle, _outputBufferAvailableCb, IntPtr.Zero); MultimediaDebug.AssertNoError(ret); } private void UnregisterOutputAvailableCallback() { int ret = Interop.MediaCodec.UnsetOutputBufferAvailableCb(_handle); MultimediaDebug.AssertNoError(ret); } #endregion #region InputProcessed event private Interop.MediaCodec.InputBufferUsedCallback _inputBufferUsedCb; /// /// Occurs when an input packet is processed. /// /// public event EventHandler InputProcessed; private void RegisterInputProcessed() { _inputBufferUsedCb = (lockedPacketHandle, _) => { MediaPacket packet = null; // Lock must be disposed here, note that the packet won't be disposed. using (MediaPacket.Lock packetLock = MediaPacket.Lock.FromHandle(lockedPacketHandle)) { Debug.Assert(packetLock != null); packet = packetLock.MediaPacket; } Debug.Assert(packet != null); InputProcessed?.Invoke(this, new InputProcessedEventArgs(packet)); }; int ret = Interop.MediaCodec.SetInputBufferUsedCb(_handle, _inputBufferUsedCb, IntPtr.Zero); MultimediaDebug.AssertNoError(ret); } private void UnregisterInputProcessed() { int ret = Interop.MediaCodec.UnsetInputBufferUsedCb(_handle); MultimediaDebug.AssertNoError(ret); } #endregion #region ErrorOccurred event private Interop.MediaCodec.ErrorCallback _errorCb; /// /// Occurs whenever an error is produced in the codec. /// public event EventHandler ErrorOccurred; private void RegisterErrorOccurred() { _errorCb = (errorCode, _) => { MediaCodecError error = (Enum.IsDefined(typeof(MediaCodecError), errorCode)) ? (MediaCodecError)errorCode : MediaCodecError.InternalError; ErrorOccurred?.Invoke(this, new MediaCodecErrorOccurredEventArgs(error)); }; int ret = Interop.MediaCodec.SetErrorCb(_handle, _errorCb, IntPtr.Zero); MultimediaDebug.AssertNoError(ret); } private void UnregisterErrorOccurred() { int ret = Interop.MediaCodec.UnsetErrorCb(_handle); MultimediaDebug.AssertNoError(ret); } #endregion #region EosReached event private EventHandler _eosReached; private Interop.MediaCodec.EosCallback _eosCb; // TODO replace /// /// Occurs when the codec processes all input data. /// public event EventHandler EosReached { add { ValidateNotDisposed(); if (_eosReached == null) { RegisterEosReached(); } _eosReached += value; } remove { ValidateNotDisposed(); _eosReached -= value; if (_eosReached == null) { UnregisterEosReached(); } } } private void RegisterEosReached() { _eosCb = _ => _eosReached?.Invoke(this, EventArgs.Empty); int ret = Interop.MediaCodec.SetEosCb(_handle, _eosCb, IntPtr.Zero); MultimediaDebug.AssertNoError(ret); } private void UnregisterEosReached() { int ret = Interop.MediaCodec.UnsetEosCb(_handle); MultimediaDebug.AssertNoError(ret); } #endregion #region BufferStatusChanged event private EventHandler _bufferStatusChanged; private Interop.MediaCodec.BufferStatusCallback _bufferStatusCb; /// /// Occurs when the codec needs more data or has enough data. /// public event EventHandler BufferStatusChanged { add { ValidateNotDisposed(); if (_bufferStatusChanged == null) { RegisterBufferStatusChanged(); } _bufferStatusChanged += value; } remove { ValidateNotDisposed(); _bufferStatusChanged -= value; if (_bufferStatusChanged == null) { UnregisterBufferStatusChanged(); } } } private void RegisterBufferStatusChanged() { _bufferStatusCb = (statusCode, _) => { Debug.Assert(Enum.IsDefined(typeof(MediaCodecStatus), statusCode), $"{ statusCode } is not defined in MediaCodecStatus!"); _bufferStatusChanged?.Invoke(this, new BufferStatusChangedEventArgs((MediaCodecStatus)statusCode)); }; int ret = Interop.MediaCodec.SetBufferStatusCb(_handle, _bufferStatusCb, IntPtr.Zero); MultimediaDebug.AssertNoError(ret); } private void UnregisterBufferStatusChanged() { int ret = Interop.MediaCodec.UnsetBufferStatusCb(_handle); MultimediaDebug.AssertNoError(ret); } #endregion } }