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;
20 using Native = Interop.MediaCodec;
22 namespace Tizen.Multimedia.MediaCodec
25 /// Provides a means to encode and decode the video and the audio data.
27 /// <feature>http://tizen.org/feature/multimedia.media_codec</feature>
28 /// <since_tizen> 3 </since_tizen>
29 public class MediaCodec : IDisposable
31 private const int CodecTypeMask = 0xFFFF;
32 private const int CodecKindMask = 0x3000;
33 // private const int CodecKindAudio = 0x1000; // Not used
34 private const int CodecKindVideo = 0x2000;
36 private IntPtr _handle;
39 /// Initializes a new instance of the MediaCodec class.
41 /// <feature>http://tizen.org/feature/multimedia.media_codec</feature>
42 /// <exception cref="NotSupportedException">The required feature is not supported.</exception>
43 /// <since_tizen> 3 </since_tizen>
46 Native.Create(out _handle).ThrowIfFailed("Failed to create media codec.");
48 RegisterInputProcessed();
49 RegisterErrorOccurred();
52 #region IDisposable-support
53 private bool _isDisposed = false;
56 /// Releases the resources used by the <see cref="MediaCodec"/> object.
58 /// <param name="disposing">
59 /// true to release both managed and unmanaged resources; false to release only unmanaged resources.
61 /// <since_tizen> 3 </since_tizen>
62 protected virtual void Dispose(bool disposing)
66 if (_handle != IntPtr.Zero)
68 Native.Destroy(_handle).ThrowIfFailed("Failed to destry media codec.");
69 _handle = IntPtr.Zero;
77 /// Finalizes an instance of the MediaCodec class.
85 /// Releases all resources used by the <see cref="MediaCodec"/> object.
87 /// <since_tizen> 3 </since_tizen>
92 GC.SuppressFinalize(this);
97 /// Validates if the object has already been disposed of.
99 /// <exception cref="ObjectDisposedException">The current object has been disposed of.</exception>
100 private void ValidateNotDisposed()
104 throw new ObjectDisposedException(nameof(MediaCodec));
108 private static IEnumerable<MediaFormatVideoMimeType> _supportedVideoCodecs;
111 /// Gets the audio codec list that the current device supports.
113 /// <since_tizen> 3 </since_tizen>
114 public static IEnumerable<MediaFormatVideoMimeType> SupportedVideoCodecs
118 if (_supportedVideoCodecs == null)
120 LoadSupportedCodec();
123 return _supportedVideoCodecs;
127 private static IEnumerable<MediaFormatAudioMimeType> _supportedAudioCodecs;
130 /// Gets the audio codec list that the current device supports.
132 /// <since_tizen> 3 </since_tizen>
133 public static IEnumerable<MediaFormatAudioMimeType> SupportedAudioCodecs
137 if (_supportedAudioCodecs == null)
139 LoadSupportedCodec();
142 return _supportedAudioCodecs;
146 private static bool TryGetMimeTypeFromCodecType<T>(int codecType, ref T result)
153 foreach (T value in Enum.GetValues(typeof(T)))
155 if ((Convert.ToInt32(value) & CodecTypeMask) == codecType)
162 Debug.Fail($"Unknown codec : { codecType }.");
166 private static void LoadSupportedCodec()
168 var videoCodecList = new List<MediaFormatVideoMimeType>();
169 var audioCodecList = new List<MediaFormatAudioMimeType>();
171 Native.SupportedCodecCallback cb = (codecType, _) =>
173 if ((codecType & CodecKindMask) == CodecKindVideo)
175 MediaFormatVideoMimeType mimeType = 0;
176 if (TryGetMimeTypeFromCodecType(codecType, ref mimeType))
178 videoCodecList.Add(mimeType);
183 MediaFormatAudioMimeType mimeType = 0;
184 if (TryGetMimeTypeFromCodecType(codecType, ref mimeType))
186 audioCodecList.Add(mimeType);
193 Native.ForeachSupportedCodec(cb, IntPtr.Zero).ThrowIfFailed("Failed to get supported codec.");
195 _supportedVideoCodecs = videoCodecList.AsReadOnly();
196 _supportedAudioCodecs = audioCodecList.AsReadOnly();
200 /// Prepares the MediaCodec for encoding or decoding.
202 /// <feature>http://tizen.org/feature/multimedia.media_codec</feature>
203 /// <exception cref="NotSupportedException">The required feature is not supported.</exception>
204 /// <exception cref="InvalidOperationException">
205 /// The codec is not configured yet.<br/>
209 /// <since_tizen> 3 </since_tizen>
210 public void Prepare()
212 ValidateNotDisposed();
214 Native.Prepare(_handle).ThrowIfFailed("Failed to prepare media codec.");
218 /// Unprepares the MediaCodec.
220 /// <feature>http://tizen.org/feature/multimedia.media_codec</feature>
221 /// <exception cref="NotSupportedException">The required feature is not supported.</exception>
222 /// <since_tizen> 3 </since_tizen>
223 public void Unprepare()
225 ValidateNotDisposed();
227 Native.Unprepare(_handle).ThrowIfFailed("Failed to unprepare media codec.");
231 /// Configures the MediaCodec.
233 /// <param name="format">The <see cref="MediaFormat"/> for properties of media data to decode or encode.</param>
234 /// <param name="encoder">The value indicating whether the codec works as an encoder or a decoder.</param>
235 /// <param name="codecType">The value indicating whether the codec uses hardware acceleration.</param>
236 /// <feature>http://tizen.org/feature/multimedia.media_codec</feature>
237 /// <exception cref="NotSupportedException">The required feature is not supported.</exception>
238 /// <exception cref="ArgumentNullException"><paramref name="format"/> is null.</exception>
239 /// <exception cref="ArgumentException">
240 /// <paramref name="codecType"/> is invalid.<br/>
242 /// <paramref name="format"/> is neither audio type nor video type.
244 /// <exception cref="NotSupportedException">The mime type of the format is not supported.</exception>
245 /// <see cref="SupportedAudioCodecs"/>
246 /// <see cref="SupportedVideoCodecs"/>
247 /// <since_tizen> 3 </since_tizen>
248 public void Configure(MediaFormat format, bool encoder, MediaCodecTypes codecType)
250 ValidateNotDisposed();
254 throw new ArgumentNullException(nameof(format));
257 if (codecType != MediaCodecTypes.Hardware && codecType != MediaCodecTypes.Software)
259 throw new ArgumentException("codecType is invalid.");
262 if (format.Type == MediaFormatType.Audio)
264 ConfigureAudio((AudioMediaFormat)format, encoder, codecType);
266 else if (format.Type == MediaFormatType.Video)
268 ConfigureVideo((VideoMediaFormat)format, encoder, codecType);
272 throw new ArgumentException("Only video and audio formats are allowed.");
276 private void ConfigureAudio(AudioMediaFormat format, bool encoder,
277 MediaCodecTypes supportType)
279 int codecType = (int)format.MimeType & CodecTypeMask;
281 if (!Enum.IsDefined(typeof(SupportedCodecType), codecType))
283 throw new NotSupportedException("The format is not supported " +
284 $"mime type : { Enum.GetName(typeof(MediaFormatAudioMimeType), format.MimeType) }");
287 DoConfigure(codecType, encoder, supportType);
291 Native.SetAudioEncoderInfo(_handle, format.SampleRate, format.Channel, format.Bit, format.BitRate).
292 ThrowIfFailed("Failed to set audio encoder information.");
296 Native.SetAudioDecoderInfo(_handle, format.SampleRate, format.Channel, format.Bit).
297 ThrowIfFailed("Failed to set audio decoder information.");
301 private void ConfigureVideo(VideoMediaFormat format, bool encoder,
302 MediaCodecTypes supportType)
304 int codecType = (int)format.MimeType & CodecTypeMask;
306 if (!Enum.IsDefined(typeof(SupportedCodecType), codecType))
308 throw new NotSupportedException("The format is not supported." +
309 $"mime type : { Enum.GetName(typeof(MediaFormatVideoMimeType), format.MimeType) }");
312 DoConfigure(codecType, encoder, supportType);
316 Native.SetVideoEncoderInfo(_handle, format.Size.Width, format.Size.Height, format.FrameRate, format.BitRate / 1000).
317 ThrowIfFailed("Failed to set video encoder information.");
321 Native.SetVideoDecoderInfo(_handle, format.Size.Width, format.Size.Height).
322 ThrowIfFailed("Failed to set video decoder information.");
326 private void DoConfigure(int codecType, bool encoder, MediaCodecTypes supportType)
328 Debug.Assert(Enum.IsDefined(typeof(SupportedCodecType), codecType));
330 int flags = (int)(encoder ? MediaCodecCodingType.Encoder : MediaCodecCodingType.Decoder);
332 flags |= (int)supportType;
334 Native.Configure(_handle, codecType, flags).ThrowIfFailed("Failed to configure media codec.");
338 /// Adds the packet to the internal queue of the codec.
340 /// <param name="packet">The packet to be encoded or decoded.</param>
341 /// <feature>http://tizen.org/feature/multimedia.media_codec</feature>
342 /// <exception cref="NotSupportedException">The required feature is not supported.</exception>
343 /// <exception cref="ArgumentNullException"><paramref name="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 fail until the <see cref="InputProcessed"/> event for the packet is invoked.</remarks>
346 /// <since_tizen> 3 </since_tizen>
347 public void ProcessInput(MediaPacket packet)
349 ValidateNotDisposed();
353 throw new ArgumentNullException(nameof(packet));
356 MediaPacket.Lock packetLock = MediaPacket.Lock.Get(packet);
358 Native.Process(_handle, packetLock.GetHandle(), 0).ThrowIfFailed("Failed to process input."); ;
362 /// Flushes both input and output buffers.
364 /// <feature>http://tizen.org/feature/multimedia.media_codec</feature>
365 /// <exception cref="NotSupportedException">The required feature is not supported.</exception>
366 /// <since_tizen> 3 </since_tizen>
367 public void FlushBuffers()
369 ValidateNotDisposed();
371 Native.FlushBuffers(_handle).ThrowIfFailed("Failed to flush buffers.");
375 /// Retrieves supported codec types for the specified params.
377 /// <param name="encoder">The value indicating encoder or decoder.</param>
378 /// <param name="type">The mime type to query.</param>
379 /// <returns>The values indicating which codec types are supported on the current device.</returns>
380 /// <feature>http://tizen.org/feature/multimedia.media_codec</feature>
381 /// <exception cref="NotSupportedException">The required feature is not supported.</exception>
382 /// <exception cref="ArgumentException"><paramref name="type"/> is invalid.</exception>
383 /// <since_tizen> 3 </since_tizen>
384 public MediaCodecTypes GetCodecType(bool encoder, MediaFormatVideoMimeType type)
386 ValidateNotDisposed();
388 if (CheckMimeType(typeof(MediaFormatVideoMimeType), (int)type) == false)
393 return GetCodecType((int)type, encoder);
397 /// Retrieves supported codec types for the specified params.
399 /// <param name="encoder">The value indicating encoder or decoder.</param>
400 /// <param name="type">The mime type to query.</param>
401 /// <returns>The values indicating which codec types are supported on the current device.</returns>
402 /// <feature>http://tizen.org/feature/multimedia.media_codec</feature>
403 /// <exception cref="NotSupportedException">The required feature is not supported.</exception>
404 /// <exception cref="ArgumentException"><paramref name="type"/> is invalid.</exception>
405 /// <since_tizen> 3 </since_tizen>
406 public MediaCodecTypes GetCodecType(bool encoder, MediaFormatAudioMimeType type)
408 ValidateNotDisposed();
410 if (CheckMimeType(typeof(MediaFormatAudioMimeType), (int)type) == false)
415 return GetCodecType((int)type, encoder);
418 private MediaCodecTypes GetCodecType(int mimeType, bool isEncoder)
420 int codecType = mimeType & CodecTypeMask;
422 Native.GetSupportedType(_handle, codecType, isEncoder, out int value).
423 ThrowIfFailed("Failed to get supported media codec type.");
425 return (MediaCodecTypes)value;
428 private bool CheckMimeType(Type type, int value)
430 int codecType = value & CodecTypeMask;
432 if (!Enum.IsDefined(type, value))
434 throw new ArgumentException($"The mime type value is invalid : { value }.");
437 return Enum.IsDefined(typeof(SupportedCodecType), codecType);
440 #region OutputAvailable event
441 private EventHandler<OutputAvailableEventArgs> _outputAvailable;
442 private Native.OutputBufferAvailableCallback _outputBufferAvailableCb;
443 private object _outputAvailableLock = new object();
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 /// <since_tizen> 3 </since_tizen>
450 public event EventHandler<OutputAvailableEventArgs> OutputAvailable
454 ValidateNotDisposed();
456 lock (_outputAvailableLock)
458 if (_outputAvailable == null)
460 RegisterOutputAvailableCallback();
462 _outputAvailable += value;
467 ValidateNotDisposed();
469 lock (_outputAvailableLock)
471 _outputAvailable -= value;
472 if (_outputAvailable == null)
474 // We can remove handler first, because we know the method that unregisters callback does not throw.
475 UnregisterOutputAvailableCallback();
481 private void RegisterOutputAvailableCallback()
483 _outputBufferAvailableCb = (packetHandle, _) =>
485 if (_outputAvailable == null)
489 Native.Destroy(packetHandle).ThrowIfFailed("Failed to destroy packet.");
493 // Do not throw exception in pinvoke callback.
499 OutputAvailableEventArgs args = null;
502 args = new OutputAvailableEventArgs(packetHandle);
508 Native.Destroy(packetHandle).ThrowIfFailed("Failed to destroy packet.");
512 // Do not throw exception in pinvoke callback.
515 MultimediaLog.Error(typeof(MediaCodec).FullName, "Failed to raise OutputAvailable event", e);
520 _outputAvailable?.Invoke(this, args);
524 Native.SetOutputBufferAvailableCb(_handle, _outputBufferAvailableCb).
525 ThrowIfFailed("Failed to set output buffer available callback.");
528 private void UnregisterOutputAvailableCallback()
530 Native.UnsetOutputBufferAvailableCb(_handle).ThrowIfFailed("Failed to unregister output available callback.");
534 #region InputProcessed event
535 private Native.InputBufferUsedCallback _inputBufferUsedCb;
538 /// Occurs when an input packet is processed.
540 /// <see cref="ProcessInput(MediaPacket)"/>
541 /// <since_tizen> 3 </since_tizen>
542 public event EventHandler<InputProcessedEventArgs> InputProcessed;
544 private void RegisterInputProcessed()
546 _inputBufferUsedCb = (lockedPacketHandle, _) =>
548 MediaPacket packet = null;
550 // Lock must be disposed here, note that the packet won't be disposed.
551 using (MediaPacket.Lock packetLock =
552 MediaPacket.Lock.FromHandle(lockedPacketHandle))
554 Debug.Assert(packetLock != null);
556 packet = packetLock.MediaPacket;
558 Debug.Assert(packet != null);
560 InputProcessed?.Invoke(this, new InputProcessedEventArgs(packet));
563 Native.SetInputBufferUsedCb(_handle, _inputBufferUsedCb).
564 ThrowIfFailed("Failed to set input buffer used callback.");
568 #region ErrorOccurred event
569 private Native.ErrorCallback _errorCb;
572 /// Occurs whenever an error is produced in the codec.
574 /// <since_tizen> 3 </since_tizen>
575 public event EventHandler<MediaCodecErrorOccurredEventArgs> ErrorOccurred;
577 private void RegisterErrorOccurred()
579 _errorCb = (errorCode, _) =>
581 MediaCodecError error = (Enum.IsDefined(typeof(MediaCodecError), errorCode)) ?
582 (MediaCodecError)errorCode : MediaCodecError.InternalError;
584 ErrorOccurred?.Invoke(this, new MediaCodecErrorOccurredEventArgs(error));
586 Native.SetErrorCb(_handle, _errorCb).ThrowIfFailed("Failed to set error callback.");
590 #region EosReached event
591 private Native.EosCallback _eosCb;
594 /// Occurs when the codec processes all input data.
596 /// <since_tizen> 3 </since_tizen>
597 public event EventHandler<EventArgs> EosReached;
599 private void RegisterEosReached()
601 _eosCb = _ => EosReached?.Invoke(this, EventArgs.Empty);
603 Native.SetEosCb(_handle, _eosCb).ThrowIfFailed("Failed to set eos callback.");
607 #region BufferStatusChanged event
608 private Native.BufferStatusCallback _bufferStatusCb;
611 /// Occurs when the codec needs more data or has enough data.
613 /// <since_tizen> 3 </since_tizen>
614 public event EventHandler<BufferStatusChangedEventArgs> BufferStatusChanged;
616 private void RegisterBufferStatusChanged()
618 _bufferStatusCb = (statusCode, _) =>
620 Debug.Assert(Enum.IsDefined(typeof(MediaCodecStatus), statusCode),
621 $"{ statusCode } is not defined in MediaCodecStatus!");
623 BufferStatusChanged?.Invoke(this,
624 new BufferStatusChangedEventArgs((MediaCodecStatus)statusCode));
627 Native.SetBufferStatusCb(_handle, _bufferStatusCb).
628 ThrowIfFailed("Failed to set buffer status callback.");