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 a means to encode and decode the video and the 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 /// Initializes 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;
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 protected virtual void Dispose(bool disposing)
66 if (_handle != IntPtr.Zero)
68 Interop.MediaCodec.Destroy(_handle);
69 _handle = IntPtr.Zero;
77 /// Finalizes an instance of the MediaCodec class.
85 /// Releases all resources used by the <see cref="MediaCodec"/> object.
91 GC.SuppressFinalize(this);
96 /// Validates if the object has already been disposed of.
98 /// <exception cref="ObjectDisposedException">The current object has been disposed of.</exception>
99 private void ValidateNotDisposed()
103 throw new ObjectDisposedException(nameof(MediaCodec));
107 private static IEnumerable<MediaFormatVideoMimeType> _supportedVideoCodecs;
110 /// Gets the audio codec list that the current device supports.
112 public static IEnumerable<MediaFormatVideoMimeType> SupportedVideoCodecs
116 if (_supportedVideoCodecs == null)
118 LoadSupportedCodec();
121 return _supportedVideoCodecs;
125 private static IEnumerable<MediaFormatAudioMimeType> _supportedAudioCodecs;
129 /// Gets the audio codec list that the current device supports.
131 public static IEnumerable<MediaFormatAudioMimeType> SupportedAudioCodecs
135 if (_supportedAudioCodecs == null)
137 LoadSupportedCodec();
140 return _supportedAudioCodecs;
144 private static bool TryGetMimeTypeFromCodecType<T>(int codecType, ref T result)
151 foreach (T value in Enum.GetValues(typeof(T)))
153 if ((Convert.ToInt32(value) & CodecTypeMask) == codecType)
160 Debug.Fail($"Unknown codec : { codecType }.");
164 private static void LoadSupportedCodec()
166 var videoCodecList = new List<MediaFormatVideoMimeType>();
167 var audioCodecList = new List<MediaFormatAudioMimeType>();
169 Interop.MediaCodec.SupportedCodecCallback cb = (codecType, _) =>
171 if ((codecType & CodecKindMask) == CodecKindVideo)
173 MediaFormatVideoMimeType mimeType = 0;
174 if (TryGetMimeTypeFromCodecType(codecType, ref mimeType))
176 videoCodecList.Add(mimeType);
181 MediaFormatAudioMimeType mimeType = 0;
182 if (TryGetMimeTypeFromCodecType(codecType, ref mimeType))
184 audioCodecList.Add(mimeType);
191 int ret = Interop.MediaCodec.ForeachSupportedCodec(cb, IntPtr.Zero);
193 MultimediaDebug.AssertNoError(ret);
195 _supportedVideoCodecs = videoCodecList.AsReadOnly();
196 _supportedAudioCodecs = audioCodecList.AsReadOnly();
200 /// Prepares the MediaCodec for encoding or decoding.
202 /// <exception cref="InvalidOperationException">
203 /// The codec is not configured yet.<br/>
207 public void Prepare()
209 ValidateNotDisposed();
211 int ret = Interop.MediaCodec.Prepare(_handle);
213 if (ret == (int)MediaCodecErrorCode.NotInitialized)
215 throw new InvalidOperationException("The codec is not configured.");
217 if (ret != (int)MediaCodecErrorCode.None)
219 throw new InvalidOperationException("Operation failed.");
222 MultimediaDebug.AssertNoError(ret);
226 /// Unprepares the MediaCodec.
228 public void Unprepare()
230 ValidateNotDisposed();
232 int ret = Interop.MediaCodec.Unprepare(_handle);
234 MultimediaDebug.AssertNoError(ret);
238 /// Configures the MediaCodec.
240 /// <param name="format">The <see cref="MediaFormat"/> for properties of media data to decode or encode.</param>
241 /// <param name="encoder">The value indicating whether the codec works as an encoder or a decoder.</param>
242 /// <param name="codecType">The value indicating whether the codec uses hardware acceleration.</param>
243 /// <exception cref="ArgumentNullException"><paramref name="format"/> is null.</exception>
244 /// <exception cref="ArgumentException">
245 /// <paramref name="codecType"/> is invalid.<br/>
247 /// <paramref name="format"/> is neither audio type nor video type.
249 /// <exception cref="NotSupportedException">The mime type of the format is not supported.</exception>
250 /// <see cref="SupportedAudioCodecs"/>
251 /// <see cref="SupportedVideoCodecs"/>
252 public void Configure(MediaFormat format, bool encoder, MediaCodecTypes codecType)
254 ValidateNotDisposed();
258 throw new ArgumentNullException(nameof(format));
261 if (codecType != MediaCodecTypes.Hardware && codecType != MediaCodecTypes.Software)
263 throw new ArgumentException("codecType is invalid.");
266 if (format.Type == MediaFormatType.Audio)
268 ConfigureAudio((AudioMediaFormat)format, encoder, codecType);
270 else if (format.Type == MediaFormatType.Video)
272 ConfigureVideo((VideoMediaFormat)format, encoder, codecType);
276 throw new ArgumentException("Only video and audio formats are allowed.");
280 private void ConfigureAudio(AudioMediaFormat format, bool encoder,
281 MediaCodecTypes supportType)
283 int codecType = (int)format.MimeType & CodecTypeMask;
285 if (!Enum.IsDefined(typeof(SupportedCodecType), codecType))
287 throw new NotSupportedException("The format is not supported " +
288 $"mime type : { Enum.GetName(typeof(MediaFormatAudioMimeType), format.MimeType) }");
291 DoConfigure(codecType, encoder, supportType);
295 int ret = Interop.MediaCodec.SetAudioEncoderInfo(_handle, format.SampleRate,
296 format.Channel, format.Bit, format.BitRate);
298 MultimediaDebug.AssertNoError(ret);
302 int ret = Interop.MediaCodec.SetAudioDecoderInfo(_handle, format.SampleRate,
303 format.Channel, format.Bit);
305 MultimediaDebug.AssertNoError(ret);
309 private void ConfigureVideo(VideoMediaFormat format, bool encoder,
310 MediaCodecTypes supportType)
312 int codecType = (int)format.MimeType & CodecTypeMask;
314 if (!Enum.IsDefined(typeof(SupportedCodecType), codecType))
316 throw new NotSupportedException("The format is not supported." +
317 $"mime type : { Enum.GetName(typeof(MediaFormatVideoMimeType), format.MimeType) }");
320 DoConfigure(codecType, encoder, supportType);
324 int ret = Interop.MediaCodec.SetVideoEncoderInfo(_handle, format.Size.Width,
325 format.Size.Height, format.FrameRate, format.BitRate / 1000);
327 MultimediaDebug.AssertNoError(ret);
331 int ret = Interop.MediaCodec.SetVideoDecoderInfo(_handle, format.Size.Width, format.Size.Height);
333 MultimediaDebug.AssertNoError(ret);
337 private void DoConfigure(int codecType, bool encoder, MediaCodecTypes supportType)
339 Debug.Assert(Enum.IsDefined(typeof(SupportedCodecType), codecType));
341 int flags = (int)(encoder ? MediaCodecCodingType.Encoder : MediaCodecCodingType.Decoder);
343 flags |= (int)supportType;
345 int ret = Interop.MediaCodec.Configure(_handle, codecType, flags);
347 if (ret == (int)MediaCodecErrorCode.NotSupportedOnDevice)
349 throw new NotSupportedException("The format is not supported.");
351 MultimediaDebug.AssertNoError(ret);
355 /// Adds the packet to the internal queue of the codec.
357 /// <param name="packet">The packet to be encoded or decoded.</param>
358 /// <exception cref="ArgumentNullException"><paramref name="packet"/> is null.</exception>
359 /// <exception cref="InvalidOperationException">The current codec is not prepared yet.</exception>
360 /// <remarks>Any attempts to modify the packet will fail until the <see cref="InputProcessed"/> event for the packet is invoked.</remarks>
361 public void ProcessInput(MediaPacket packet)
363 ValidateNotDisposed();
367 throw new ArgumentNullException(nameof(packet));
370 MediaPacket.Lock packetLock = MediaPacket.Lock.Get(packet);
372 int ret = Interop.MediaCodec.Process(_handle, packetLock.GetHandle(), 0);
374 if (ret == (int)MediaCodecErrorCode.InvalidState)
376 throw new InvalidOperationException("The codec is in invalid state.");
379 MultimediaDebug.AssertNoError(ret);
383 /// Flushes both input and output buffers.
385 public void FlushBuffers()
387 ValidateNotDisposed();
389 int ret = Interop.MediaCodec.FlushBuffers(_handle);
391 MultimediaDebug.AssertNoError(ret);
395 /// Retrieves supported codec types for the specified params.
397 /// <param name="encoder">The value indicating encoder or decoder.</param>
398 /// <param name="type">The mime type to query.</param>
399 /// <returns>The values indicating which codec types are supported on the current device.</returns>
400 /// <exception cref="ArgumentException"><paramref name="type"/> is invalid.</exception>
401 public MediaCodecTypes GetCodecType(bool encoder, MediaFormatVideoMimeType type)
403 ValidateNotDisposed();
405 if (CheckMimeType(typeof(MediaFormatVideoMimeType), (int)type) == false)
410 return GetCodecType((int)type, encoder);
414 /// Retrieves supported codec types for the specified params.
416 /// <param name="encoder">The value indicating encoder or decoder.</param>
417 /// <param name="type">The mime type to query.</param>
418 /// <returns>The values indicating which codec types are supported on the current device.</returns>
419 /// <exception cref="ArgumentException"><paramref name="type"/> is invalid.</exception>
420 public MediaCodecTypes GetCodecType(bool encoder, MediaFormatAudioMimeType type)
422 ValidateNotDisposed();
424 if (CheckMimeType(typeof(MediaFormatAudioMimeType), (int)type) == false)
429 return GetCodecType((int)type, encoder);
432 private MediaCodecTypes GetCodecType(int mimeType, bool isEncoder)
434 int codecType = mimeType & CodecTypeMask;
437 int ret = Interop.MediaCodec.GetSupportedType(_handle, codecType, isEncoder, out value);
439 MultimediaDebug.AssertNoError(ret);
441 return (MediaCodecTypes)value;
444 private bool CheckMimeType(Type type, int value)
446 int codecType = value & CodecTypeMask;
448 if (!Enum.IsDefined(type, value))
450 throw new ArgumentException($"The mime type value is invalid : { value }.");
453 return Enum.IsDefined(typeof(SupportedCodecType), codecType);
456 #region OutputAvailable event
457 private EventHandler<OutputAvailableEventArgs> _outputAvailable;
458 private Interop.MediaCodec.OutputBufferAvailableCallback _outputBufferAvailableCb;
459 private object _outputAvailableLock = new object();
462 /// Occurs when an output buffer is available.
464 /// <remarks>The output packet needs to be disposed after it is used to clean up unmanaged resources.</remarks>
465 public event EventHandler<OutputAvailableEventArgs> OutputAvailable
469 ValidateNotDisposed();
471 lock (_outputAvailableLock)
473 if (_outputAvailable == null)
475 RegisterOutputAvailableCallback();
477 _outputAvailable += value;
482 ValidateNotDisposed();
484 lock (_outputAvailableLock)
486 _outputAvailable -= value;
487 if (_outputAvailable == null)
489 // We can remove handler first, because we know the method that unregisters callback does not throw.
490 UnregisterOutputAvailableCallback();
496 private void RegisterOutputAvailableCallback()
498 _outputBufferAvailableCb = (packetHandle, _) =>
500 if (_outputAvailable == null)
502 Interop.MediaPacket.Destroy(packetHandle);
506 OutputAvailableEventArgs args = null;
509 args = new OutputAvailableEventArgs(packetHandle);
513 Interop.MediaPacket.Destroy(packetHandle);
515 MultimediaLog.Error(typeof(MediaCodec).FullName, "Failed to raise OutputAvailable event", e);
520 _outputAvailable?.Invoke(this, args);
524 int ret = Interop.MediaCodec.SetOutputBufferAvailableCb(_handle, _outputBufferAvailableCb);
526 MultimediaDebug.AssertNoError(ret);
529 private void UnregisterOutputAvailableCallback()
531 int ret = Interop.MediaCodec.UnsetOutputBufferAvailableCb(_handle);
533 MultimediaDebug.AssertNoError(ret);
537 #region InputProcessed event
538 private Interop.MediaCodec.InputBufferUsedCallback _inputBufferUsedCb;
541 /// Occurs when an input packet is processed.
543 /// <see cref="ProcessInput(MediaPacket)"/>
544 public event EventHandler<InputProcessedEventArgs> InputProcessed;
546 private void RegisterInputProcessed()
548 _inputBufferUsedCb = (lockedPacketHandle, _) =>
550 MediaPacket packet = null;
552 // Lock must be disposed here, note that the packet won't be disposed.
553 using (MediaPacket.Lock packetLock =
554 MediaPacket.Lock.FromHandle(lockedPacketHandle))
556 Debug.Assert(packetLock != null);
558 packet = packetLock.MediaPacket;
560 Debug.Assert(packet != null);
562 InputProcessed?.Invoke(this, new InputProcessedEventArgs(packet));
565 int ret = Interop.MediaCodec.SetInputBufferUsedCb(_handle, _inputBufferUsedCb);
567 MultimediaDebug.AssertNoError(ret);
571 #region ErrorOccurred event
572 private Interop.MediaCodec.ErrorCallback _errorCb;
575 /// Occurs whenever an error is produced in the codec.
577 public event EventHandler<MediaCodecErrorOccurredEventArgs> ErrorOccurred;
579 private void RegisterErrorOccurred()
581 _errorCb = (errorCode, _) =>
583 MediaCodecError error = (Enum.IsDefined(typeof(MediaCodecError), errorCode)) ?
584 (MediaCodecError)errorCode : MediaCodecError.InternalError;
586 ErrorOccurred?.Invoke(this, new MediaCodecErrorOccurredEventArgs(error));
588 int ret = Interop.MediaCodec.SetErrorCb(_handle, _errorCb);
590 MultimediaDebug.AssertNoError(ret);
594 #region EosReached event
595 private Interop.MediaCodec.EosCallback _eosCb;
598 /// Occurs when the codec processes all input data.
600 public event EventHandler<EventArgs> EosReached;
602 private void RegisterEosReached()
604 _eosCb = _ => EosReached?.Invoke(this, EventArgs.Empty);
606 int ret = Interop.MediaCodec.SetEosCb(_handle, _eosCb);
608 MultimediaDebug.AssertNoError(ret);
613 #region BufferStatusChanged event
614 private Interop.MediaCodec.BufferStatusCallback _bufferStatusCb;
617 /// Occurs when the codec needs more data or has enough data.
619 public event EventHandler<BufferStatusChangedEventArgs> BufferStatusChanged;
621 private void RegisterBufferStatusChanged()
623 _bufferStatusCb = (statusCode, _) =>
625 Debug.Assert(Enum.IsDefined(typeof(MediaCodecStatus), statusCode),
626 $"{ statusCode } is not defined in MediaCodecStatus!");
628 BufferStatusChanged?.Invoke(this,
629 new BufferStatusChangedEventArgs((MediaCodecStatus)statusCode));
632 int ret = Interop.MediaCodec.SetBufferStatusCb(_handle, _bufferStatusCb);
634 MultimediaDebug.AssertNoError(ret);