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 const int CodecTypeMask = 0xFFFF;
29 private const int CodecKindMask = 0x3000;
30 // private const int CodecKindAudio = 0x1000; // Not used
31 private const 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 MultimediaDebug.AssertNoError(ret);
49 RegisterInputProcessed();
50 RegisterErrorOccurred();
53 #region IDisposable-support
54 private bool _isDisposed = false;
56 protected virtual void Dispose(bool disposing)
60 if (_handle != IntPtr.Zero)
62 Interop.MediaCodec.Destroy(_handle);
63 _handle = IntPtr.Zero;
79 GC.SuppressFinalize(this);
84 /// Validates if the object already has been disposed of.
86 /// <exception cref="ObjectDisposedException">The current object has been disposed of.</exception>
87 private void ValidateNotDisposed()
91 throw new ObjectDisposedException(nameof(MediaCodec));
95 private static IEnumerable<MediaFormatVideoMimeType> _supportedVideoCodecs;
98 /// Gets the audio codec list that the current device supports.
100 public static IEnumerable<MediaFormatVideoMimeType> SupportedVideoCodecs
104 if (_supportedVideoCodecs == null)
106 LoadSupportedCodec();
109 return _supportedVideoCodecs;
113 private static IEnumerable<MediaFormatAudioMimeType> _supportedAudioCodecs;
117 /// Gets the audio codec list that the current device supports.
119 public static IEnumerable<MediaFormatAudioMimeType> SupportedAudioCodecs
123 if (_supportedAudioCodecs == null)
125 LoadSupportedCodec();
128 return _supportedAudioCodecs;
132 private static bool TryGetMimeTypeFromCodecType<T>(int codecType, ref T result)
139 foreach (T value in Enum.GetValues(typeof(T)))
141 if ((Convert.ToInt32(value) & CodecTypeMask) == codecType)
148 Debug.Fail($"Unknown codec : { codecType }.");
152 private static void LoadSupportedCodec()
154 var videoCodecList = new List<MediaFormatVideoMimeType>();
155 var audioCodecList = new List<MediaFormatAudioMimeType>();
157 Interop.MediaCodec.SupportedCodecCallback cb = (codecType, _) =>
159 if ((codecType & CodecKindMask) == CodecKindVideo)
161 MediaFormatVideoMimeType mimeType = 0;
162 if (TryGetMimeTypeFromCodecType(codecType, ref mimeType))
164 videoCodecList.Add(mimeType);
169 MediaFormatAudioMimeType mimeType = 0;
170 if (TryGetMimeTypeFromCodecType(codecType, ref mimeType))
172 audioCodecList.Add(mimeType);
179 int ret = Interop.MediaCodec.ForeachSupportedCodec(cb, IntPtr.Zero);
181 MultimediaDebug.AssertNoError(ret);
183 _supportedVideoCodecs = videoCodecList.AsReadOnly();
184 _supportedAudioCodecs = audioCodecList.AsReadOnly();
188 /// Prepares the MediaCodec for encoding or decoding.
190 /// <exception cref="InvalidOperationException">
191 /// The codec is not configured, yet.\n
195 public void Prepare()
197 ValidateNotDisposed();
199 int ret = Interop.MediaCodec.Prepare(_handle);
201 if (ret == (int)MediaCodecErrorCode.NotInitialized)
203 throw new InvalidOperationException("The codec is not configured.");
205 if (ret != (int)MediaCodecErrorCode.None)
207 throw new InvalidOperationException("Operation failed.");
210 MultimediaDebug.AssertNoError(ret);
214 /// Unprepares the MediaCodec.
216 public void Unprepare()
218 ValidateNotDisposed();
220 int ret = Interop.MediaCodec.Unprepare(_handle);
222 MultimediaDebug.AssertNoError(ret);
226 /// Configures the MediaCodec.
228 /// <param name="format">The <see cref="MediaFormat"/> for properties of media data to decode or encode.</param>
229 /// <param name="encoder">The value indicating whether the codec works as a encoder or a decoder.</param>
230 /// <param name="codecType">The value indicating whether the codec uses hardware acceleration.</param>
231 /// <exception cref="ArgumentNullException">format is null</exception>
232 /// <exception cref="ArgumentException">
233 /// <paramref name="codecType"/> is invalid.\n
235 /// <paramref name="format"/> is neither audio type nor video type.
237 /// <exception cref="NotSupportedException">the mime type of the format is not supported.</exception>
238 /// <see cref="SupportedAudioCodecs"/>
239 /// <see cref="SupportedVideoCodecs"/>
240 public void Configure(MediaFormat format, bool encoder, MediaCodecTypes codecType)
242 ValidateNotDisposed();
246 throw new ArgumentNullException(nameof(format));
249 if (codecType != MediaCodecTypes.Hardware && codecType != MediaCodecTypes.Software)
251 throw new ArgumentException("codecType is invalid.");
254 if (format.Type == MediaFormatType.Audio)
256 ConfigureAudio((AudioMediaFormat)format, encoder, codecType);
258 else if (format.Type == MediaFormatType.Video)
260 ConfigureVideo((VideoMediaFormat)format, encoder, codecType);
264 throw new ArgumentException("Only video and audio formats are allowed.");
268 private void ConfigureAudio(AudioMediaFormat format, bool encoder,
269 MediaCodecTypes supportType)
271 int codecType = (int)format.MimeType & CodecTypeMask;
273 if (!Enum.IsDefined(typeof(SupportedCodecType), codecType))
275 throw new NotSupportedException("The format is not supported " +
276 $"mime type : { Enum.GetName(typeof(MediaFormatAudioMimeType), format.MimeType) }");
279 DoConfigure(codecType, encoder, supportType);
283 int ret = Interop.MediaCodec.SetAudioEncoderInfo(_handle, format.SampleRate,
284 format.Channel, format.Bit, format.BitRate);
286 MultimediaDebug.AssertNoError(ret);
290 int ret = Interop.MediaCodec.SetAudioDecoderInfo(_handle, format.SampleRate,
291 format.Channel, format.Bit);
293 MultimediaDebug.AssertNoError(ret);
297 private void ConfigureVideo(VideoMediaFormat format, bool encoder,
298 MediaCodecTypes supportType)
300 int codecType = (int)format.MimeType & CodecTypeMask;
302 if (!Enum.IsDefined(typeof(SupportedCodecType), codecType))
304 throw new NotSupportedException("The format is not supported." +
305 $"mime type : { Enum.GetName(typeof(MediaFormatVideoMimeType), format.MimeType) }");
308 DoConfigure(codecType, encoder, supportType);
312 int ret = Interop.MediaCodec.SetVideoEncoderInfo(_handle, format.Size.Width,
313 format.Size.Height, format.FrameRate, format.BitRate / 1000);
315 MultimediaDebug.AssertNoError(ret);
319 int ret = Interop.MediaCodec.SetVideoDecoderInfo(_handle, format.Size.Width, format.Size.Height);
321 MultimediaDebug.AssertNoError(ret);
325 private void DoConfigure(int codecType, bool encoder, MediaCodecTypes supportType)
327 Debug.Assert(Enum.IsDefined(typeof(SupportedCodecType), codecType));
329 int flags = (int)(encoder ? MediaCodecCodingType.Encoder : MediaCodecCodingType.Decoder);
331 flags |= (int)supportType;
333 int ret = Interop.MediaCodec.Configure(_handle, codecType, flags);
335 if (ret == (int)MediaCodecErrorCode.NotSupportedOnDevice)
337 throw new NotSupportedException("The format is not supported.");
339 MultimediaDebug.AssertNoError(ret);
343 /// Adds the packet to the internal queue of the codec.
345 /// <param name="packet">The packet to be encoded or decoded.</param>
346 /// <exception cref="ArgumentNullException">packet is null.</exception>
347 /// <exception cref="InvalidOperationException">the current codec is not prepared, yet.</exception>
348 /// <remarks>Any attempts to modify the packet will be failed until the <see cref="InputProcessed"/> event for the packet is invoked.</remarks>
349 public void ProcessInput(MediaPacket packet)
351 ValidateNotDisposed();
355 throw new ArgumentNullException(nameof(packet));
358 MediaPacket.Lock packetLock = MediaPacket.Lock.Get(packet);
360 int ret = Interop.MediaCodec.Process(_handle, packetLock.GetHandle(), 0);
362 if (ret == (int)MediaCodecErrorCode.InvalidState)
364 throw new InvalidOperationException("The codec is in invalid state.");
367 MultimediaDebug.AssertNoError(ret);
371 /// Flushes both input and output buffers.
373 public void FlushBuffers()
375 ValidateNotDisposed();
377 int ret = Interop.MediaCodec.FlushBuffers(_handle);
379 MultimediaDebug.AssertNoError(ret);
383 /// Retrieves supported codec types for the specified params.
385 /// <param name="encoder">The value indicating encoder or decoder.</param>
386 /// <param name="type">The mime type to query.</param>
387 /// <returns>The values indicating which codec types are supported on the current device.</returns>
388 /// <exception cref="ArgumentException">type is invalid.</exception>
389 public MediaCodecTypes GetCodecType(bool encoder, MediaFormatVideoMimeType type)
391 ValidateNotDisposed();
393 if (CheckMimeType(typeof(MediaFormatVideoMimeType), (int)type) == false)
398 return GetCodecType((int)type, encoder);
402 /// Retrieves supported codec types for the specified params.
404 /// <param name="encoder">The value indicating encoder or decoder.</param>
405 /// <param name="type">The mime type to query.</param>
406 /// <returns>The values indicating which codec types are supported on the current device.</returns>
407 /// <exception cref="ArgumentException">type is invalid.</exception>
408 public MediaCodecTypes GetCodecType(bool encoder, MediaFormatAudioMimeType type)
410 ValidateNotDisposed();
412 if (CheckMimeType(typeof(MediaFormatAudioMimeType), (int)type) == false)
417 return GetCodecType((int)type, encoder);
420 private MediaCodecTypes GetCodecType(int mimeType, bool isEncoder)
422 int codecType = mimeType & CodecTypeMask;
425 int ret = Interop.MediaCodec.GetSupportedType(_handle, codecType, isEncoder, out value);
427 MultimediaDebug.AssertNoError(ret);
429 return (MediaCodecTypes)value;
432 private bool CheckMimeType(Type type, int value)
434 int codecType = value & CodecTypeMask;
436 if (!Enum.IsDefined(type, value))
438 throw new ArgumentException($"The mime type value is invalid : { value }.");
441 return Enum.IsDefined(typeof(SupportedCodecType), codecType);
444 #region OutputAvailable event
445 private EventHandler<OutputAvailableEventArgs> _outputAvailable;
446 private Interop.MediaCodec.OutputBufferAvailableCallback _outputBufferAvailableCb;
449 /// Occurs when an output buffer is available.
451 /// <remarks>The output packet needs to be disposed after it is used to clean up unmanaged resources.</remarks>
452 public event EventHandler<OutputAvailableEventArgs> OutputAvailable
456 ValidateNotDisposed();
458 if (_outputAvailable == null)
460 RegisterOutputAvailableCallback();
462 _outputAvailable += value;
467 ValidateNotDisposed();
469 _outputAvailable -= value;
470 if (_outputAvailable == null)
472 UnregisterOutputAvailableCallback();
477 private void RegisterOutputAvailableCallback()
479 _outputBufferAvailableCb = (packetHandle, _) =>
481 OutputAvailableEventArgs args = null;
485 args = new OutputAvailableEventArgs(packetHandle);
489 Interop.MediaPacket.Destroy(packetHandle);
491 // TODO should we throw it to unmanaged code?
495 //TODO dispose if no event handler registered
496 _outputAvailable?.Invoke(this, args);
499 int ret = Interop.MediaCodec.SetOutputBufferAvailableCb(_handle,
500 _outputBufferAvailableCb, IntPtr.Zero);
502 MultimediaDebug.AssertNoError(ret);
505 private void UnregisterOutputAvailableCallback()
507 int ret = Interop.MediaCodec.UnsetOutputBufferAvailableCb(_handle);
509 MultimediaDebug.AssertNoError(ret);
513 #region InputProcessed event
514 private Interop.MediaCodec.InputBufferUsedCallback _inputBufferUsedCb;
517 /// Occurs when an input packet is processed.
519 /// <see cref="ProcessInput(MediaPacket)"/>
520 public event EventHandler<InputProcessedEventArgs> InputProcessed;
522 private void RegisterInputProcessed()
524 _inputBufferUsedCb = (lockedPacketHandle, _) =>
526 MediaPacket packet = null;
528 // Lock must be disposed here, note that the packet won't be disposed.
529 using (MediaPacket.Lock packetLock =
530 MediaPacket.Lock.FromHandle(lockedPacketHandle))
532 Debug.Assert(packetLock != null);
534 packet = packetLock.MediaPacket;
536 Debug.Assert(packet != null);
538 InputProcessed?.Invoke(this, new InputProcessedEventArgs(packet));
541 int ret = Interop.MediaCodec.SetInputBufferUsedCb(_handle,
542 _inputBufferUsedCb, IntPtr.Zero);
544 MultimediaDebug.AssertNoError(ret);
547 private void UnregisterInputProcessed()
549 int ret = Interop.MediaCodec.UnsetInputBufferUsedCb(_handle);
551 MultimediaDebug.AssertNoError(ret);
555 #region ErrorOccurred event
556 private Interop.MediaCodec.ErrorCallback _errorCb;
559 /// Occurs whenever an error is produced in the codec.
561 public event EventHandler<MediaCodecErrorOccurredEventArgs> ErrorOccurred;
563 private void RegisterErrorOccurred()
565 _errorCb = (errorCode, _) =>
567 MediaCodecError error = (Enum.IsDefined(typeof(MediaCodecError), errorCode)) ?
568 (MediaCodecError)errorCode : MediaCodecError.InternalError;
570 ErrorOccurred?.Invoke(this, new MediaCodecErrorOccurredEventArgs(error));
572 int ret = Interop.MediaCodec.SetErrorCb(_handle, _errorCb, IntPtr.Zero);
574 MultimediaDebug.AssertNoError(ret);
577 private void UnregisterErrorOccurred()
579 int ret = Interop.MediaCodec.UnsetErrorCb(_handle);
581 MultimediaDebug.AssertNoError(ret);
585 #region EosReached event
586 private EventHandler<EventArgs> _eosReached;
587 private Interop.MediaCodec.EosCallback _eosCb;
591 /// Occurs when the codec processes all input data.
593 public event EventHandler<EventArgs> EosReached
597 ValidateNotDisposed();
599 if (_eosReached == null)
601 RegisterEosReached();
603 _eosReached += value;
608 ValidateNotDisposed();
610 _eosReached -= value;
611 if (_eosReached == null)
613 UnregisterEosReached();
618 private void RegisterEosReached()
620 _eosCb = _ => _eosReached?.Invoke(this, EventArgs.Empty);
622 int ret = Interop.MediaCodec.SetEosCb(_handle, _eosCb, IntPtr.Zero);
624 MultimediaDebug.AssertNoError(ret);
627 private void UnregisterEosReached()
629 int ret = Interop.MediaCodec.UnsetEosCb(_handle);
631 MultimediaDebug.AssertNoError(ret);
635 #region BufferStatusChanged event
636 private EventHandler<BufferStatusChangedEventArgs> _bufferStatusChanged;
637 private Interop.MediaCodec.BufferStatusCallback _bufferStatusCb;
640 /// Occurs when the codec needs more data or has enough data.
642 public event EventHandler<BufferStatusChangedEventArgs> BufferStatusChanged
646 ValidateNotDisposed();
648 if (_bufferStatusChanged == null)
650 RegisterBufferStatusChanged();
652 _bufferStatusChanged += value;
657 ValidateNotDisposed();
659 _bufferStatusChanged -= value;
660 if (_bufferStatusChanged == null)
662 UnregisterBufferStatusChanged();
667 private void RegisterBufferStatusChanged()
669 _bufferStatusCb = (statusCode, _) =>
671 Debug.Assert(Enum.IsDefined(typeof(MediaCodecStatus), statusCode),
672 $"{ statusCode } is not defined in MediaCodecStatus!");
674 _bufferStatusChanged?.Invoke(this,
675 new BufferStatusChangedEventArgs((MediaCodecStatus)statusCode));
678 int ret = Interop.MediaCodec.SetBufferStatusCb(_handle, _bufferStatusCb, IntPtr.Zero);
680 MultimediaDebug.AssertNoError(ret);
683 private void UnregisterBufferStatusChanged()
685 int ret = Interop.MediaCodec.UnsetBufferStatusCb(_handle);
687 MultimediaDebug.AssertNoError(ret);