/* * 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 the video and the 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; /// /// Initializes 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 has already 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 an encoder or a decoder. /// The value indicating whether the codec uses hardware acceleration. /// 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. /// is null. /// The current codec is not prepared yet. /// Any attempts to modify the packet will fail 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. /// 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. /// 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; private object _outputAvailableLock = new object(); /// /// 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(); lock (_outputAvailableLock) { if (_outputAvailable == null) { RegisterOutputAvailableCallback(); } _outputAvailable += value; } } remove { ValidateNotDisposed(); lock (_outputAvailableLock) { _outputAvailable -= value; if (_outputAvailable == null) { // We can remove handler first, because we know the method that unregisters callback does not throw. UnregisterOutputAvailableCallback(); } } } } private void RegisterOutputAvailableCallback() { _outputBufferAvailableCb = (packetHandle, _) => { if (_outputAvailable == null) { Interop.MediaPacket.Destroy(packetHandle); return; } OutputAvailableEventArgs args = null; try { args = new OutputAvailableEventArgs(packetHandle); } catch (Exception e) { Interop.MediaPacket.Destroy(packetHandle); MultimediaLog.Error(typeof(MediaCodec).FullName, "Failed to raise OutputAvailable event", e); } if (args != null) { _outputAvailable?.Invoke(this, args); } }; int ret = Interop.MediaCodec.SetOutputBufferAvailableCb(_handle, _outputBufferAvailableCb); 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); 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); MultimediaDebug.AssertNoError(ret); } #endregion #region EosReached event private Interop.MediaCodec.EosCallback _eosCb; /// /// Occurs when the codec processes all input data. /// public event EventHandler EosReached; private void RegisterEosReached() { _eosCb = _ => EosReached?.Invoke(this, EventArgs.Empty); int ret = Interop.MediaCodec.SetEosCb(_handle, _eosCb); MultimediaDebug.AssertNoError(ret); } #endregion #region BufferStatusChanged event private Interop.MediaCodec.BufferStatusCallback _bufferStatusCb; /// /// Occurs when the codec needs more data or has enough data. /// public event EventHandler BufferStatusChanged; 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); MultimediaDebug.AssertNoError(ret); } #endregion } }