2 * Copyright (c) 2016 Samsung Electronics Co., Ltd All Rights Reserved
4 * Licensed under the Apache License, Version 2.0 (the License);
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an AS IS BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 using System.Collections.Generic;
19 using System.Diagnostics;
21 namespace Tizen.Multimedia.MediaCodec
24 /// Provides the means to encode and decode video and audio data.
26 public class MediaCodec : IDisposable
28 private static readonly int CodecTypeMask = 0xFFFF;
29 private static readonly int CodecKindMask = 0x3000;
30 private static readonly int CodecKindAudio = 0x1000;
31 private static readonly int CodecKindVideo = 0x2000;
33 private IntPtr _handle;
36 /// Initialize a new instance of the MediaCodec class.
40 int ret = Interop.MediaCodec.Create(out _handle);
42 if (ret == (int)MediaCodecErrorCode.InvalidOperation)
44 throw new InvalidOperationException("Not able to initialize a new media codec.");
47 MediaCodecDebug.AssertNoError(ret);
50 #region IDisposable-support
51 private bool _isDisposed = false;
53 protected virtual void Dispose(bool disposing)
57 if (_handle != IntPtr.Zero)
59 Interop.MediaCodec.Destroy(_handle);
60 _handle = IntPtr.Zero;
76 GC.SuppressFinalize(this);
81 /// Validates if the object already has been disposed of.
83 /// <exception cref="ObjectDisposedException">The current object has been disposed of.</exception>
84 private void ValidateNotDisposed()
88 throw new ObjectDisposedException(nameof(MediaCodec));
92 private static IEnumerable<MediaFormatVideoMimeType> _supportedVideoCodecs;
95 /// Gets the audio codec list that the current device supports.
97 public static IEnumerable<MediaFormatVideoMimeType> SupportedVideoCodecs
101 if (_supportedVideoCodecs == null)
103 LoadSupportedCodec();
106 return _supportedVideoCodecs;
110 private static IEnumerable<MediaFormatAudioMimeType> _supportedAudioCodecs;
114 /// Gets the audio codec list that the current device supports.
116 public static IEnumerable<MediaFormatAudioMimeType> SupportedAudioCodecs
120 if (_supportedAudioCodecs == null)
122 LoadSupportedCodec();
125 return _supportedAudioCodecs;
129 private static bool TryGetMimeTypeFromCodecType<T>(int codecType, ref T result)
136 foreach (T value in Enum.GetValues(typeof(T)))
138 if ((Convert.ToInt32(value) & CodecTypeMask) == codecType)
145 Debug.Fail($"Unknown codec : { codecType }.");
149 private static void LoadSupportedCodec()
151 var videoCodecList = new List<MediaFormatVideoMimeType>();
152 var audioCodecList = new List<MediaFormatAudioMimeType>();
154 Interop.MediaCodec.SupportedCodecCallback cb = (int codecType, IntPtr arg) =>
156 if ((codecType & CodecKindMask) == CodecKindVideo)
158 MediaFormatVideoMimeType mimeType = 0;
159 if (TryGetMimeTypeFromCodecType(codecType, ref mimeType))
161 videoCodecList.Add(mimeType);
166 MediaFormatAudioMimeType mimeType = 0;
167 if (TryGetMimeTypeFromCodecType(codecType, ref mimeType))
169 audioCodecList.Add(mimeType);
176 int ret = Interop.MediaCodec.ForeachSupportedCodec(cb, IntPtr.Zero);
178 MediaCodecDebug.AssertNoError(ret);
180 _supportedVideoCodecs = videoCodecList.AsReadOnly();
181 _supportedAudioCodecs = audioCodecList.AsReadOnly();
185 /// Prepares the MediaCodec for encoding or decoding.
187 /// <exception cref="InvalidOperationException">
188 /// The codec is not configured, yet.
189 /// <para>-or-</para>
192 public void Prepare()
194 ValidateNotDisposed();
196 int ret = Interop.MediaCodec.Prepare(_handle);
198 if (ret == (int)MediaCodecErrorCode.NotInitialized)
200 throw new InvalidOperationException("The codec is not configured.");
202 if (ret != (int)MediaCodecErrorCode.None)
204 throw new InvalidOperationException("Operation failed.");
207 MediaCodecDebug.AssertNoError(ret);
211 /// Unprepares the MediaCodec.
213 public void Unprepare()
215 ValidateNotDisposed();
217 int ret = Interop.MediaCodec.Unprepare(_handle);
219 MediaCodecDebug.AssertNoError(ret);
223 /// Configures the MediaCodec.
225 /// <param name="format">The <see cref="MediaFormat"/> for properties of media data to decode or encode.</param>
226 /// <param name="encoder">The value indicating whether the codec works as a encoder or a decoder.</param>
227 /// <param name="codecType">The value indicating whether the codec uses hardware acceleration.</param>
228 /// <exception cref="ArgumentNullException">format is null</exception>
229 /// <exception cref="ArgumentException">
230 /// codecType is invalid.
231 /// <para>-or-</para>
232 /// format is neither audio type or video type.
234 /// <exception cref="NotSupportedException">the mime type of the format is not supported.</exception>
235 /// <see cref="SupportedAudioCodecs"/>
236 /// <see cref="SupportedVideoCodecs"/>
237 public void Configure(MediaFormat format, bool encoder, MediaCodecTypes codecType)
239 ValidateNotDisposed();
243 throw new ArgumentNullException(nameof(format));
246 if (codecType != MediaCodecTypes.Hardware && codecType != MediaCodecTypes.Software)
248 throw new ArgumentException("codecType is invalid.");
251 if (format.Type == MediaFormatType.Audio)
253 ConfigureAudio((AudioMediaFormat)format, encoder, codecType);
255 else if (format.Type == MediaFormatType.Video)
257 ConfigureVideo((VideoMediaFormat)format, encoder, codecType);
261 throw new ArgumentException("Only video and audio formats are allowed.");
265 private void ConfigureAudio(AudioMediaFormat format, bool encoder,
266 MediaCodecTypes supportType)
268 int codecType = (int)format.MimeType & CodecTypeMask;
270 if (!Enum.IsDefined(typeof(SupportedCodecType), codecType))
272 throw new NotSupportedException("The format is not supported " +
273 $"mime type : { Enum.GetName(typeof(MediaFormatAudioMimeType), format.MimeType) }");
276 DoConfigure(codecType, encoder, supportType);
280 int ret = Interop.MediaCodec.SetAudioEncoderInfo(_handle, format.SampleRate,
281 format.Channel, format.Bit, format.BitRate);
283 MediaCodecDebug.AssertNoError(ret);
287 int ret = Interop.MediaCodec.SetAudioDecoderInfo(_handle, format.SampleRate,
288 format.Channel, format.Bit);
290 MediaCodecDebug.AssertNoError(ret);
294 private void ConfigureVideo(VideoMediaFormat format, bool encoder,
295 MediaCodecTypes supportType)
297 int codecType = (int)format.MimeType & CodecTypeMask;
299 if (!Enum.IsDefined(typeof(SupportedCodecType), codecType))
301 throw new NotSupportedException("The format is not supported." +
302 $"mime type : { Enum.GetName(typeof(MediaFormatVideoMimeType), format.MimeType) }");
305 DoConfigure(codecType, encoder, supportType);
309 int ret = Interop.MediaCodec.SetVideoEncoderInfo(_handle, format.Width,
310 format.Height, format.FrameRate, format.BitRate / 1000);
312 MediaCodecDebug.AssertNoError(ret);
316 int ret = Interop.MediaCodec.SetVideoDecoderInfo(_handle, format.Width, format.Height);
318 MediaCodecDebug.AssertNoError(ret);
322 private void DoConfigure(int codecType, bool encoder, MediaCodecTypes supportType)
324 Debug.Assert(Enum.IsDefined(typeof(SupportedCodecType), codecType));
326 int flags = (int)(encoder ? MediaCodecCodingType.Encoder : MediaCodecCodingType.Decoder);
328 flags |= (int)supportType;
330 int ret = Interop.MediaCodec.Configure(_handle, codecType, flags);
332 if (ret == (int)MediaCodecErrorCode.NotSupportedOnDevice)
334 throw new NotSupportedException("The format is not supported.");
336 MediaCodecDebug.AssertNoError(ret);
340 /// Add the packet to the internal queue of the codec.
342 /// <param name="packet">The packet to be encoded or decoded.</param>
343 /// <exception cref="ArgumentNullException">packet is null.</exception>
344 /// <exception cref="InvalidOperationException">the current codec is not prepared, yet.</exception>
345 /// <remarks>Any attempts to modify the packet will be failed until the <see cref="InputProcessed"/> event for the packet is invoked.</remarks>
346 public void ProcessInput(MediaPacket packet)
348 ValidateNotDisposed();
352 throw new ArgumentNullException(nameof(packet));
355 MediaPacket.Lock packetLock = new MediaPacket.Lock(packet);
357 int ret = Interop.MediaCodec.Process(_handle, packetLock.GetHandle(), 0);
359 if (ret == (int)MediaCodecErrorCode.InvalidState)
361 throw new InvalidOperationException("The codec is in invalid state.");
364 MediaCodecDebug.AssertNoError(ret);
368 /// Flush both input and output buffers.
370 public void FlushBuffers()
372 ValidateNotDisposed();
374 int ret = Interop.MediaCodec.FlushBuffers(_handle);
376 MediaCodecDebug.AssertNoError(ret);
380 /// Retrieves supported codec types for the specified params.
382 /// <param name="encoder">The value indicating encoder or decoder.</param>
383 /// <param name="type">The mime type to query.</param>
384 /// <returns>The values indicating which codec types are supported on the current device.</returns>
385 /// <exception cref="ArgumentException">type is invalid.</exception>
386 public MediaCodecTypes GetCodecType(bool encoder, MediaFormatVideoMimeType type)
388 ValidateNotDisposed();
390 if (CheckMimeType(typeof(MediaFormatVideoMimeType), (int)type) == false)
395 return GetCodecType((int)type, encoder);
399 /// Retrieves supported codec types for the specified params.
401 /// <param name="encoder">The value indicating encoder or decoder.</param>
402 /// <param name="type">The mime type to query.</param>
403 /// <returns>The values indicating which codec types are supported on the current device.</returns>
404 /// <exception cref="ArgumentException">type is invalid.</exception>
405 public MediaCodecTypes GetCodecType(bool encoder, MediaFormatAudioMimeType type)
407 ValidateNotDisposed();
409 if (CheckMimeType(typeof(MediaFormatAudioMimeType), (int)type) == false)
414 return GetCodecType((int)type, encoder);
417 private MediaCodecTypes GetCodecType(int mimeType, bool isEncoder)
419 int codecType = mimeType & CodecTypeMask;
422 int ret = Interop.MediaCodec.GetSupportedType(_handle, codecType, isEncoder, out value);
424 MediaCodecDebug.AssertNoError(ret);
426 return (MediaCodecTypes)value;
429 private bool CheckMimeType(Type type, int value)
431 int codecType = value & CodecTypeMask;
433 if (!Enum.IsDefined(type, value))
435 throw new ArgumentException($"The mime type value is invalid : { value }.");
438 return Enum.IsDefined(typeof(SupportedCodecType), codecType);
441 #region OutputAvailable event
442 private EventHandler<OutputAvailableEventArgs> _outputAvailable;
443 private Interop.MediaCodec.OutputBufferAvailableCallback _outputBufferAvailableCb;
446 /// Occurs when an output buffer is available.
448 /// <remarks>The output packet needs to be disposed after it is used to clean up unmanaged resources.</remarks>
449 public event EventHandler<OutputAvailableEventArgs> OutputAvailable
453 ValidateNotDisposed();
455 if (_outputAvailable == null)
457 RegisterOutputAvailableCallback();
459 _outputAvailable += value;
464 ValidateNotDisposed();
466 _outputAvailable -= value;
467 if (_outputAvailable == null)
469 UnregisterOutputAvailableCallback();
474 private void RegisterOutputAvailableCallback()
476 _outputBufferAvailableCb = (IntPtr packetHandle, IntPtr arg) =>
478 OutputAvailableEventArgs args = null;
482 args = new OutputAvailableEventArgs(packetHandle);
486 Interop.MediaPacket.Destroy(packetHandle);
488 // TODO should we throw it to unmanaged code?
492 //TODO dispose if no event handler registered
493 _outputAvailable?.Invoke(this, args);
496 int ret = Interop.MediaCodec.SetOutputBufferAvaiableCb(_handle,
497 _outputBufferAvailableCb, IntPtr.Zero);
499 MediaCodecDebug.AssertNoError(ret);
502 private void UnregisterOutputAvailableCallback()
504 int ret = Interop.MediaCodec.UnsetOutputBufferAvaiableCb(_handle);
506 MediaCodecDebug.AssertNoError(ret);
510 #region InputProcessed event
511 private EventHandler<InputProcessedEventArgs> _inputProcessed;
512 private Interop.MediaCodec.InputBufferUsedCallback _inputBufferUsedCb;
515 /// Occurs when an input packet is processed.
517 /// <see cref="ProcessInput(MediaPacket)"/>
518 public event EventHandler<InputProcessedEventArgs> InputProcessed
522 ValidateNotDisposed();
524 if (_inputProcessed == null)
526 RegisterInputProcessed();
528 _inputProcessed += value;
533 ValidateNotDisposed();
535 _inputProcessed -= value;
536 if (_inputProcessed == null)
538 UnregisterInputProcessed();
543 private void RegisterInputProcessed()
545 _inputBufferUsedCb = (IntPtr lockedPacketHandle, IntPtr arg) =>
547 MediaPacket packet = null;
549 // Lock must be disposed here, note that the packet won't be disposed.
550 using (MediaPacket.Lock packetLock =
551 MediaPacket.Lock.FromHandle(lockedPacketHandle))
553 packet = packetLock.MediaPacket;
555 Debug.Assert(packet != null);
557 _inputProcessed?.Invoke(this, new InputProcessedEventArgs(packet));
560 int ret = Interop.MediaCodec.SetInputBufferUsedCb(_handle,
561 _inputBufferUsedCb, IntPtr.Zero);
563 MediaCodecDebug.AssertNoError(ret);
566 private void UnregisterInputProcessed()
568 int ret = Interop.MediaCodec.UnsetInputBufferUsedCb(_handle);
570 MediaCodecDebug.AssertNoError(ret);
574 #region ErrorOccurred event
575 private EventHandler<ErrorOccurredEventArgs> _errorOccurred;
576 private Interop.MediaCodec.ErrorCallback _errorCb;
579 /// Occurs whenever an error is produced in the codec.
581 public event EventHandler<ErrorOccurredEventArgs> ErrorOccurred
585 ValidateNotDisposed();
587 if (_errorOccurred == null)
589 RegisterErrorOccurred();
591 _errorOccurred += value;
596 ValidateNotDisposed();
598 _errorOccurred -= value;
599 if (_errorOccurred == null)
601 UnregisterErrorOccurred();
606 private void RegisterErrorOccurred()
608 _errorCb = (int errorCode, IntPtr arg) =>
610 MediaCodecError error = (Enum.IsDefined(typeof(MediaCodecError), errorCode)) ?
611 (MediaCodecError)errorCode : MediaCodecError.InternalError;
613 _errorOccurred?.Invoke(this, new ErrorOccurredEventArgs(error));
615 int ret = Interop.MediaCodec.SetErrorCb(_handle, _errorCb, IntPtr.Zero);
617 MediaCodecDebug.AssertNoError(ret);
620 private void UnregisterErrorOccurred()
622 int ret = Interop.MediaCodec.UnsetErrorCb(_handle);
624 MediaCodecDebug.AssertNoError(ret);
628 #region EosReached event
629 private EventHandler<EosReachedEventArgs> _eosReached;
630 private Interop.MediaCodec.EosCallback _eosCb;
633 /// Occurs when the codec processes all input data.
635 public event EventHandler<EosReachedEventArgs> EosReached
639 ValidateNotDisposed();
641 if (_eosReached == null)
643 RegisterEosReached();
645 _eosReached += value;
650 ValidateNotDisposed();
652 _eosReached -= value;
653 if (_eosReached == null)
655 UnregisterEosReached();
660 private void RegisterEosReached()
662 _eosCb = (IntPtr arg) => _eosReached?.Invoke(this, new EosReachedEventArgs());
664 int ret = Interop.MediaCodec.SetEosCb(_handle, _eosCb, IntPtr.Zero);
666 MediaCodecDebug.AssertNoError(ret);
669 private void UnregisterEosReached()
671 int ret = Interop.MediaCodec.UnsetEosCb(_handle);
673 MediaCodecDebug.AssertNoError(ret);
677 #region BufferStatusChanged event
678 private EventHandler<BufferStatusChangedEventArgs> _bufferStatusChanged;
679 private Interop.MediaCodec.BufferStatusCallback _bufferStatusCb;
682 /// Occurs when the codec needs more data or has enough data.
684 public event EventHandler<BufferStatusChangedEventArgs> BufferStatusChanged
688 ValidateNotDisposed();
690 if (_bufferStatusChanged == null)
692 RegisterBufferStatusChanged();
694 _bufferStatusChanged += value;
699 ValidateNotDisposed();
701 _bufferStatusChanged -= value;
702 if (_bufferStatusChanged == null)
704 UnregisterBufferStatusChanged();
709 private void RegisterBufferStatusChanged()
711 _bufferStatusCb = (int statusCode, IntPtr arg) =>
713 Debug.Assert(Enum.IsDefined(typeof(MediaCodecStatus), statusCode),
714 $"{ statusCode } is not defined in MediaCodecStatus!");
716 _bufferStatusChanged?.Invoke(this,
717 new BufferStatusChangedEventArgs((MediaCodecStatus)statusCode));
720 int ret = Interop.MediaCodec.SetBufferStatusCb(_handle, _bufferStatusCb, IntPtr.Zero);
722 MediaCodecDebug.AssertNoError(ret);
725 private void UnregisterBufferStatusChanged()
727 int ret = Interop.MediaCodec.UnsetBufferStatusCb(_handle);
729 MediaCodecDebug.AssertNoError(ret);