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();
50 RegisterBufferStatusChanged();
53 #region IDisposable-support
54 private bool _isDisposed = false;
57 /// Releases the resources used by the <see cref="MediaCodec"/> object.
59 /// <param name="disposing">
60 /// true to release both managed and unmanaged resources; false to release only unmanaged resources.
62 /// <since_tizen> 3 </since_tizen>
63 protected virtual void Dispose(bool disposing)
67 if (_handle != IntPtr.Zero)
69 Native.Destroy(_handle).ThrowIfFailed("Failed to destry media codec.");
70 _handle = IntPtr.Zero;
78 /// Finalizes an instance of the MediaCodec class.
86 /// Releases all resources used by the <see cref="MediaCodec"/> object.
88 /// <since_tizen> 3 </since_tizen>
93 GC.SuppressFinalize(this);
98 /// Validates if the object has already been disposed of.
100 /// <exception cref="ObjectDisposedException">The current object has been disposed of.</exception>
101 private void ValidateNotDisposed()
105 throw new ObjectDisposedException(nameof(MediaCodec));
109 private static IEnumerable<MediaFormatVideoMimeType> _supportedVideoCodecs;
112 /// Gets the audio codec list that the current device supports.
114 /// <since_tizen> 3 </since_tizen>
115 public static IEnumerable<MediaFormatVideoMimeType> SupportedVideoCodecs
119 if (_supportedVideoCodecs == null)
121 LoadSupportedCodec();
124 return _supportedVideoCodecs;
128 private static IEnumerable<MediaFormatAudioMimeType> _supportedAudioCodecs;
131 /// Gets the audio codec list that the current device supports.
133 /// <since_tizen> 3 </since_tizen>
134 public static IEnumerable<MediaFormatAudioMimeType> SupportedAudioCodecs
138 if (_supportedAudioCodecs == null)
140 LoadSupportedCodec();
143 return _supportedAudioCodecs;
147 private static bool TryGetMimeTypeFromCodecType<T>(int codecType, ref T result)
154 foreach (T value in Enum.GetValues(typeof(T)))
156 if ((Convert.ToInt32(value) & CodecTypeMask) == codecType)
163 Debug.Fail($"Unknown codec : { codecType }.");
167 private static void LoadSupportedCodec()
169 var videoCodecList = new List<MediaFormatVideoMimeType>();
170 var audioCodecList = new List<MediaFormatAudioMimeType>();
172 Native.SupportedCodecCallback cb = (codecType, _) =>
174 if ((codecType & CodecKindMask) == CodecKindVideo)
176 MediaFormatVideoMimeType mimeType = 0;
177 if (TryGetMimeTypeFromCodecType(codecType, ref mimeType))
179 videoCodecList.Add(mimeType);
184 MediaFormatAudioMimeType mimeType = 0;
185 if (TryGetMimeTypeFromCodecType(codecType, ref mimeType))
187 audioCodecList.Add(mimeType);
194 Native.ForeachSupportedCodec(cb, IntPtr.Zero).ThrowIfFailed("Failed to get supported codec.");
196 _supportedVideoCodecs = videoCodecList.AsReadOnly();
197 _supportedAudioCodecs = audioCodecList.AsReadOnly();
201 /// Prepares the MediaCodec for encoding or decoding.
203 /// <feature>http://tizen.org/feature/multimedia.media_codec</feature>
204 /// <exception cref="NotSupportedException">The required feature is not supported.</exception>
205 /// <exception cref="InvalidOperationException">
206 /// The codec is not configured yet.<br/>
210 /// <since_tizen> 3 </since_tizen>
211 public void Prepare()
213 ValidateNotDisposed();
215 Native.Prepare(_handle).ThrowIfFailed("Failed to prepare media codec.");
219 /// Unprepares the MediaCodec.
221 /// <feature>http://tizen.org/feature/multimedia.media_codec</feature>
222 /// <exception cref="NotSupportedException">The required feature is not supported.</exception>
223 /// <since_tizen> 3 </since_tizen>
224 public void Unprepare()
226 ValidateNotDisposed();
228 Native.Unprepare(_handle).ThrowIfFailed("Failed to unprepare media codec.");
232 /// Configures the MediaCodec.
234 /// <param name="format">The <see cref="MediaFormat"/> for properties of media data to decode or encode.</param>
235 /// <param name="encoder">The value indicating whether the codec works as an encoder or a decoder.</param>
236 /// <param name="codecType">The value indicating whether the codec uses hardware acceleration.</param>
237 /// <feature>http://tizen.org/feature/multimedia.media_codec</feature>
238 /// <exception cref="NotSupportedException">The required feature is not supported.</exception>
239 /// <exception cref="ArgumentNullException"><paramref name="format"/> is null.</exception>
240 /// <exception cref="ArgumentException">
241 /// <paramref name="codecType"/> is invalid.<br/>
243 /// <paramref name="format"/> is neither audio type nor video type.
245 /// <exception cref="NotSupportedException">The mime type of the format is not supported.</exception>
246 /// <see cref="SupportedAudioCodecs"/>
247 /// <see cref="SupportedVideoCodecs"/>
248 /// <since_tizen> 3 </since_tizen>
249 public void Configure(MediaFormat format, bool encoder, MediaCodecTypes codecType)
251 ValidateNotDisposed();
255 throw new ArgumentNullException(nameof(format));
258 if (codecType != MediaCodecTypes.Hardware && codecType != MediaCodecTypes.Software)
260 throw new ArgumentException("codecType is invalid.");
263 if (format.Type == MediaFormatType.Audio)
265 ConfigureAudio((AudioMediaFormat)format, encoder, codecType);
267 else if (format.Type == MediaFormatType.Video)
269 ConfigureVideo((VideoMediaFormat)format, encoder, codecType);
273 throw new ArgumentException("Only video and audio formats are allowed.");
277 private void ConfigureAudio(AudioMediaFormat format, bool encoder,
278 MediaCodecTypes supportType)
280 int codecType = (int)format.MimeType & CodecTypeMask;
282 if (!Enum.IsDefined(typeof(SupportedCodecType), codecType))
284 throw new NotSupportedException("The format is not supported " +
285 $"mime type : { Enum.GetName(typeof(MediaFormatAudioMimeType), format.MimeType) }");
288 DoConfigure(codecType, encoder, supportType);
292 Native.SetAudioEncoderInfo(_handle, format.SampleRate, format.Channel, format.Bit, format.BitRate).
293 ThrowIfFailed("Failed to set audio encoder information.");
297 Native.SetAudioDecoderInfo(_handle, format.SampleRate, format.Channel, format.Bit).
298 ThrowIfFailed("Failed to set audio decoder information.");
302 private void ConfigureVideo(VideoMediaFormat format, bool encoder,
303 MediaCodecTypes supportType)
305 int codecType = (int)format.MimeType & CodecTypeMask;
307 if (!Enum.IsDefined(typeof(SupportedCodecType), codecType))
309 throw new NotSupportedException("The format is not supported." +
310 $"mime type : { Enum.GetName(typeof(MediaFormatVideoMimeType), format.MimeType) }");
313 DoConfigure(codecType, encoder, supportType);
317 Native.SetVideoEncoderInfo(_handle, format.Size.Width, format.Size.Height, format.FrameRate, format.BitRate / 1000).
318 ThrowIfFailed("Failed to set video encoder information.");
322 Native.SetVideoDecoderInfo(_handle, format.Size.Width, format.Size.Height).
323 ThrowIfFailed("Failed to set video decoder information.");
327 private void DoConfigure(int codecType, bool encoder, MediaCodecTypes supportType)
329 Debug.Assert(Enum.IsDefined(typeof(SupportedCodecType), codecType));
331 int flags = (int)(encoder ? MediaCodecCodingType.Encoder : MediaCodecCodingType.Decoder);
333 flags |= (int)supportType;
335 Native.Configure(_handle, codecType, flags).ThrowIfFailed("Failed to configure media codec.");
339 /// Adds the packet to the internal queue of the codec.
341 /// <param name="packet">The packet to be encoded or decoded.</param>
342 /// <feature>http://tizen.org/feature/multimedia.media_codec</feature>
343 /// <exception cref="NotSupportedException">The required feature is not supported.</exception>
344 /// <exception cref="ArgumentNullException"><paramref name="packet"/> is null.</exception>
345 /// <exception cref="InvalidOperationException">The current codec is not prepared yet.</exception>
346 /// <remarks>Any attempts to modify the packet will fail until the <see cref="InputProcessed"/> event for the packet is invoked.</remarks>
347 /// <since_tizen> 3 </since_tizen>
348 public void ProcessInput(MediaPacket packet)
350 ValidateNotDisposed();
354 throw new ArgumentNullException(nameof(packet));
357 MediaPacket.Lock packetLock = MediaPacket.Lock.Get(packet);
359 Native.Process(_handle, packetLock.GetHandle(), 0).ThrowIfFailed("Failed to process input."); ;
363 /// Flushes both input and output buffers.
365 /// <feature>http://tizen.org/feature/multimedia.media_codec</feature>
366 /// <exception cref="NotSupportedException">The required feature is not supported.</exception>
367 /// <since_tizen> 3 </since_tizen>
368 public void FlushBuffers()
370 ValidateNotDisposed();
372 Native.FlushBuffers(_handle).ThrowIfFailed("Failed to flush buffers.");
376 /// Retrieves supported codec types for the specified params.
378 /// <param name="encoder">The value indicating encoder or decoder.</param>
379 /// <param name="type">The mime type to query.</param>
380 /// <returns>The values indicating which codec types are supported on the current device.</returns>
381 /// <feature>http://tizen.org/feature/multimedia.media_codec</feature>
382 /// <exception cref="NotSupportedException">The required feature is not supported.</exception>
383 /// <exception cref="ArgumentException"><paramref name="type"/> is invalid.</exception>
384 /// <since_tizen> 3 </since_tizen>
385 public MediaCodecTypes GetCodecType(bool encoder, MediaFormatVideoMimeType type)
387 ValidateNotDisposed();
389 if (CheckMimeType(typeof(MediaFormatVideoMimeType), (int)type) == false)
394 return GetCodecType((int)type, encoder);
398 /// Retrieves supported codec types for the specified params.
400 /// <param name="encoder">The value indicating encoder or decoder.</param>
401 /// <param name="type">The mime type to query.</param>
402 /// <returns>The values indicating which codec types are supported on the current device.</returns>
403 /// <feature>http://tizen.org/feature/multimedia.media_codec</feature>
404 /// <exception cref="NotSupportedException">The required feature is not supported.</exception>
405 /// <exception cref="ArgumentException"><paramref name="type"/> is invalid.</exception>
406 /// <since_tizen> 3 </since_tizen>
407 public MediaCodecTypes GetCodecType(bool encoder, MediaFormatAudioMimeType type)
409 ValidateNotDisposed();
411 if (CheckMimeType(typeof(MediaFormatAudioMimeType), (int)type) == false)
416 return GetCodecType((int)type, encoder);
419 private MediaCodecTypes GetCodecType(int mimeType, bool isEncoder)
421 int codecType = mimeType & CodecTypeMask;
423 Native.GetSupportedType(_handle, codecType, isEncoder, out int value).
424 ThrowIfFailed("Failed to get supported media codec type.");
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 Native.OutputBufferAvailableCallback _outputBufferAvailableCb;
444 private object _outputAvailableLock = new object();
447 /// Occurs when an output buffer is available.
449 /// <remarks>The output packet needs to be disposed after it is used to clean up unmanaged resources.</remarks>
450 /// <since_tizen> 3 </since_tizen>
451 public event EventHandler<OutputAvailableEventArgs> OutputAvailable
455 ValidateNotDisposed();
457 lock (_outputAvailableLock)
459 if (_outputAvailable == null)
461 RegisterOutputAvailableCallback();
463 _outputAvailable += value;
468 ValidateNotDisposed();
470 lock (_outputAvailableLock)
472 _outputAvailable -= value;
473 if (_outputAvailable == null)
475 // We can remove handler first, because we know the method that unregisters callback does not throw.
476 UnregisterOutputAvailableCallback();
482 private void RegisterOutputAvailableCallback()
484 _outputBufferAvailableCb = (packetHandle, _) =>
486 if (_outputAvailable == null)
490 Native.Destroy(packetHandle).ThrowIfFailed("Failed to destroy packet.");
494 // Do not throw exception in pinvoke callback.
500 OutputAvailableEventArgs args = null;
503 args = new OutputAvailableEventArgs(packetHandle);
509 Native.Destroy(packetHandle).ThrowIfFailed("Failed to destroy packet.");
513 // Do not throw exception in pinvoke callback.
516 MultimediaLog.Error(typeof(MediaCodec).FullName, "Failed to raise OutputAvailable event", e);
521 _outputAvailable?.Invoke(this, args);
525 Native.SetOutputBufferAvailableCb(_handle, _outputBufferAvailableCb).
526 ThrowIfFailed("Failed to set output buffer available callback.");
529 private void UnregisterOutputAvailableCallback()
531 Native.UnsetOutputBufferAvailableCb(_handle).ThrowIfFailed("Failed to unregister output available callback.");
535 #region InputProcessed event
536 private Native.InputBufferUsedCallback _inputBufferUsedCb;
539 /// Occurs when an input packet is processed.
541 /// <see cref="ProcessInput(MediaPacket)"/>
542 /// <since_tizen> 3 </since_tizen>
543 public event EventHandler<InputProcessedEventArgs> InputProcessed;
545 private void RegisterInputProcessed()
547 _inputBufferUsedCb = (lockedPacketHandle, _) =>
549 MediaPacket packet = null;
551 // Lock must be disposed here, note that the packet won't be disposed.
552 using (MediaPacket.Lock packetLock =
553 MediaPacket.Lock.FromHandle(lockedPacketHandle))
555 Debug.Assert(packetLock != null);
557 packet = packetLock.MediaPacket;
559 Debug.Assert(packet != null);
561 InputProcessed?.Invoke(this, new InputProcessedEventArgs(packet));
564 Native.SetInputBufferUsedCb(_handle, _inputBufferUsedCb).
565 ThrowIfFailed("Failed to set input buffer used callback.");
569 #region ErrorOccurred event
570 private Native.ErrorCallback _errorCb;
573 /// Occurs whenever an error is produced in the codec.
575 /// <since_tizen> 3 </since_tizen>
576 public event EventHandler<MediaCodecErrorOccurredEventArgs> ErrorOccurred;
578 private void RegisterErrorOccurred()
580 _errorCb = (errorCode, _) =>
582 MediaCodecError error = (Enum.IsDefined(typeof(MediaCodecError), errorCode)) ?
583 (MediaCodecError)errorCode : MediaCodecError.InternalError;
585 ErrorOccurred?.Invoke(this, new MediaCodecErrorOccurredEventArgs(error));
587 Native.SetErrorCb(_handle, _errorCb).ThrowIfFailed("Failed to set error callback.");
591 #region EosReached event
592 private Native.EosCallback _eosCb;
595 /// Occurs when the codec processes all input data.
597 /// <since_tizen> 3 </since_tizen>
598 public event EventHandler<EventArgs> EosReached;
600 private void RegisterEosReached()
602 _eosCb = _ => EosReached?.Invoke(this, EventArgs.Empty);
604 Native.SetEosCb(_handle, _eosCb).ThrowIfFailed("Failed to set eos callback.");
608 #region BufferStatusChanged event
609 private Native.BufferStatusCallback _bufferStatusCb;
612 /// Occurs when the codec needs more data or has enough data.
614 /// <since_tizen> 3 </since_tizen>
615 public event EventHandler<BufferStatusChangedEventArgs> BufferStatusChanged;
617 private void RegisterBufferStatusChanged()
619 _bufferStatusCb = (statusCode, _) =>
621 Debug.Assert(Enum.IsDefined(typeof(MediaCodecStatus), statusCode),
622 $"{ statusCode } is not defined in MediaCodecStatus!");
624 BufferStatusChanged?.Invoke(this, new BufferStatusChangedEventArgs(statusCode));
627 Native.SetBufferStatusCb(_handle, _bufferStatusCb).
628 ThrowIfFailed("Failed to set buffer status callback.");