Release 4.0.0-preview1-00201
[platform/core/csapi/tizenfx.git] / src / Tizen.Multimedia.MediaCodec / MediaCodec / MediaCodec.cs
1 /*
2  * Copyright (c) 2016 Samsung Electronics Co., Ltd All Rights Reserved
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 using System;
18 using System.Collections.Generic;
19 using System.Diagnostics;
20
21 namespace Tizen.Multimedia.MediaCodec
22 {
23     /// <summary>
24     /// Provides the means to encode and decode the video and the audio data.
25     /// </summary>
26     public class MediaCodec : IDisposable
27     {
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;
32
33         private IntPtr _handle;
34
35         /// <summary>
36         /// Initializes a new instance of the MediaCodec class.
37         /// </summary>
38         public MediaCodec()
39         {
40             int ret = Interop.MediaCodec.Create(out _handle);
41
42             if (ret == (int)MediaCodecErrorCode.InvalidOperation)
43             {
44                 throw new InvalidOperationException("Not able to initialize a new media codec.");
45             }
46
47             MultimediaDebug.AssertNoError(ret);
48
49             RegisterInputProcessed();
50             RegisterErrorOccurred();
51         }
52
53         #region IDisposable-support
54         private bool _isDisposed = false;
55
56         protected virtual void Dispose(bool disposing)
57         {
58             if (!_isDisposed)
59             {
60                 if (_handle != IntPtr.Zero)
61                 {
62                     Interop.MediaCodec.Destroy(_handle);
63                     _handle = IntPtr.Zero;
64                 }
65
66                 _isDisposed = true;
67             }
68         }
69
70         ~MediaCodec()
71         {
72             Dispose(false);
73         }
74
75         public void Dispose()
76         {
77             Dispose(true);
78
79             GC.SuppressFinalize(this);
80         }
81         #endregion
82
83         /// <summary>
84         /// Validates if the object has already been disposed of.
85         /// </summary>
86         /// <exception cref="ObjectDisposedException">The current object has been disposed of.</exception>
87         private void ValidateNotDisposed()
88         {
89             if (_isDisposed)
90             {
91                 throw new ObjectDisposedException(nameof(MediaCodec));
92             }
93         }
94
95         private static IEnumerable<MediaFormatVideoMimeType> _supportedVideoCodecs;
96
97         /// <summary>
98         /// Gets the audio codec list that the current device supports.
99         /// </summary>
100         public static IEnumerable<MediaFormatVideoMimeType> SupportedVideoCodecs
101         {
102             get
103             {
104                 if (_supportedVideoCodecs == null)
105                 {
106                     LoadSupportedCodec();
107                 }
108
109                 return _supportedVideoCodecs;
110             }
111         }
112
113         private static IEnumerable<MediaFormatAudioMimeType> _supportedAudioCodecs;
114
115
116         /// <summary>
117         /// Gets the audio codec list that the current device supports.
118         /// </summary>
119         public static IEnumerable<MediaFormatAudioMimeType> SupportedAudioCodecs
120         {
121             get
122             {
123                 if (_supportedAudioCodecs == null)
124                 {
125                     LoadSupportedCodec();
126                 }
127
128                 return _supportedAudioCodecs;
129             }
130         }
131
132         private static bool TryGetMimeTypeFromCodecType<T>(int codecType, ref T result)
133         {
134             if (codecType == -1)
135             {
136                 return false;
137             }
138
139             foreach (T value in Enum.GetValues(typeof(T)))
140             {
141                 if ((Convert.ToInt32(value) & CodecTypeMask) == codecType)
142                 {
143                     result = value;
144                     return true;
145                 }
146             }
147
148             Debug.Fail($"Unknown codec : { codecType }.");
149             return false;
150         }
151
152         private static void LoadSupportedCodec()
153         {
154             var videoCodecList = new List<MediaFormatVideoMimeType>();
155             var audioCodecList = new List<MediaFormatAudioMimeType>();
156
157             Interop.MediaCodec.SupportedCodecCallback cb = (codecType, _) =>
158             {
159                 if ((codecType & CodecKindMask) == CodecKindVideo)
160                 {
161                     MediaFormatVideoMimeType mimeType = 0;
162                     if (TryGetMimeTypeFromCodecType(codecType, ref mimeType))
163                     {
164                         videoCodecList.Add(mimeType);
165                     }
166                 }
167                 else
168                 {
169                     MediaFormatAudioMimeType mimeType = 0;
170                     if (TryGetMimeTypeFromCodecType(codecType, ref mimeType))
171                     {
172                         audioCodecList.Add(mimeType);
173                     }
174                 }
175
176                 return true;
177             };
178
179             int ret = Interop.MediaCodec.ForeachSupportedCodec(cb, IntPtr.Zero);
180
181             MultimediaDebug.AssertNoError(ret);
182
183             _supportedVideoCodecs = videoCodecList.AsReadOnly();
184             _supportedAudioCodecs = audioCodecList.AsReadOnly();
185         }
186
187         /// <summary>
188         /// Prepares the MediaCodec for encoding or decoding.
189         /// </summary>
190         /// <exception cref="InvalidOperationException">
191         ///     The codec is not configured yet.\n
192         ///     -or-\n
193         ///     Internal error.
194         /// </exception>
195         public void Prepare()
196         {
197             ValidateNotDisposed();
198
199             int ret = Interop.MediaCodec.Prepare(_handle);
200
201             if (ret == (int)MediaCodecErrorCode.NotInitialized)
202             {
203                 throw new InvalidOperationException("The codec is not configured.");
204             }
205             if (ret != (int)MediaCodecErrorCode.None)
206             {
207                 throw new InvalidOperationException("Operation failed.");
208             }
209
210             MultimediaDebug.AssertNoError(ret);
211         }
212
213         /// <summary>
214         /// Unprepares the MediaCodec.
215         /// </summary>
216         public void Unprepare()
217         {
218             ValidateNotDisposed();
219
220             int ret = Interop.MediaCodec.Unprepare(_handle);
221
222             MultimediaDebug.AssertNoError(ret);
223         }
224
225         /// <summary>
226         /// Configures the MediaCodec.
227         /// </summary>
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 an encoder or a decoder.</param>
230         /// <param name="codecType">The value indicating whether the codec uses hardware acceleration.</param>
231         /// <exception cref="ArgumentNullException"><paramref name="format"/> is null.</exception>
232         /// <exception cref="ArgumentException">
233         ///     <paramref name="codecType"/> is invalid.\n
234         ///     -or-\n
235         ///     <paramref name="format"/> is neither audio type nor video type.
236         ///     </exception>
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)
241         {
242             ValidateNotDisposed();
243
244             if (format == null)
245             {
246                 throw new ArgumentNullException(nameof(format));
247             }
248
249             if (codecType != MediaCodecTypes.Hardware && codecType != MediaCodecTypes.Software)
250             {
251                 throw new ArgumentException("codecType is invalid.");
252             }
253
254             if (format.Type == MediaFormatType.Audio)
255             {
256                 ConfigureAudio((AudioMediaFormat)format, encoder, codecType);
257             }
258             else if (format.Type == MediaFormatType.Video)
259             {
260                 ConfigureVideo((VideoMediaFormat)format, encoder, codecType);
261             }
262             else
263             {
264                 throw new ArgumentException("Only video and audio formats are allowed.");
265             }
266         }
267
268         private void ConfigureAudio(AudioMediaFormat format, bool encoder,
269             MediaCodecTypes supportType)
270         {
271             int codecType = (int)format.MimeType & CodecTypeMask;
272
273             if (!Enum.IsDefined(typeof(SupportedCodecType), codecType))
274             {
275                 throw new NotSupportedException("The format is not supported " +
276                     $"mime type : { Enum.GetName(typeof(MediaFormatAudioMimeType), format.MimeType) }");
277             }
278
279             DoConfigure(codecType, encoder, supportType);
280
281             if (encoder)
282             {
283                 int ret = Interop.MediaCodec.SetAudioEncoderInfo(_handle, format.SampleRate,
284                     format.Channel, format.Bit, format.BitRate);
285
286                 MultimediaDebug.AssertNoError(ret);
287             }
288             else
289             {
290                 int ret = Interop.MediaCodec.SetAudioDecoderInfo(_handle, format.SampleRate,
291                     format.Channel, format.Bit);
292
293                 MultimediaDebug.AssertNoError(ret);
294             }
295         }
296
297         private void ConfigureVideo(VideoMediaFormat format, bool encoder,
298             MediaCodecTypes supportType)
299         {
300             int codecType = (int)format.MimeType & CodecTypeMask;
301
302             if (!Enum.IsDefined(typeof(SupportedCodecType), codecType))
303             {
304                 throw new NotSupportedException("The format is not supported." +
305                     $"mime type : { Enum.GetName(typeof(MediaFormatVideoMimeType), format.MimeType) }");
306             }
307
308             DoConfigure(codecType, encoder, supportType);
309
310             if (encoder)
311             {
312                 int ret = Interop.MediaCodec.SetVideoEncoderInfo(_handle, format.Size.Width,
313                     format.Size.Height, format.FrameRate, format.BitRate / 1000);
314
315                 MultimediaDebug.AssertNoError(ret);
316             }
317             else
318             {
319                 int ret = Interop.MediaCodec.SetVideoDecoderInfo(_handle, format.Size.Width, format.Size.Height);
320
321                 MultimediaDebug.AssertNoError(ret);
322             }
323         }
324
325         private void DoConfigure(int codecType, bool encoder, MediaCodecTypes supportType)
326         {
327             Debug.Assert(Enum.IsDefined(typeof(SupportedCodecType), codecType));
328
329             int flags = (int)(encoder ? MediaCodecCodingType.Encoder : MediaCodecCodingType.Decoder);
330
331             flags |= (int)supportType;
332
333             int ret = Interop.MediaCodec.Configure(_handle, codecType, flags);
334
335             if (ret == (int)MediaCodecErrorCode.NotSupportedOnDevice)
336             {
337                 throw new NotSupportedException("The format is not supported.");
338             }
339             MultimediaDebug.AssertNoError(ret);
340         }
341
342         /// <summary>
343         /// Adds the packet to the internal queue of the codec.
344         /// </summary>
345         /// <param name="packet">The packet to be encoded or decoded.</param>
346         /// <exception cref="ArgumentNullException"><paramref="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 fail until the <see cref="InputProcessed"/> event for the packet is invoked.</remarks>
349         public void ProcessInput(MediaPacket packet)
350         {
351             ValidateNotDisposed();
352
353             if (packet == null)
354             {
355                 throw new ArgumentNullException(nameof(packet));
356             }
357
358             MediaPacket.Lock packetLock = MediaPacket.Lock.Get(packet);
359
360             int ret = Interop.MediaCodec.Process(_handle, packetLock.GetHandle(), 0);
361
362             if (ret == (int)MediaCodecErrorCode.InvalidState)
363             {
364                 throw new InvalidOperationException("The codec is in invalid state.");
365             }
366
367             MultimediaDebug.AssertNoError(ret);
368         }
369
370         /// <summary>
371         /// Flushes both input and output buffers.
372         /// </summary>
373         public void FlushBuffers()
374         {
375             ValidateNotDisposed();
376
377             int ret = Interop.MediaCodec.FlushBuffers(_handle);
378
379             MultimediaDebug.AssertNoError(ret);
380         }
381
382         /// <summary>
383         /// Retrieves supported codec types for the specified params.
384         /// </summary>
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"><paramref="type"/> is invalid.</exception>
389         public MediaCodecTypes GetCodecType(bool encoder, MediaFormatVideoMimeType type)
390         {
391             ValidateNotDisposed();
392
393             if (CheckMimeType(typeof(MediaFormatVideoMimeType), (int)type) == false)
394             {
395                 return 0;
396             }
397
398             return GetCodecType((int)type, encoder);
399         }
400
401         /// <summary>
402         /// Retrieves supported codec types for the specified params.
403         /// </summary>
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"><paramref="type"/> is invalid.</exception>
408         public MediaCodecTypes GetCodecType(bool encoder, MediaFormatAudioMimeType type)
409         {
410             ValidateNotDisposed();
411
412             if (CheckMimeType(typeof(MediaFormatAudioMimeType), (int)type) == false)
413             {
414                 return 0;
415             }
416
417             return GetCodecType((int)type, encoder);
418         }
419
420         private MediaCodecTypes GetCodecType(int mimeType, bool isEncoder)
421         {
422             int codecType = mimeType & CodecTypeMask;
423             int value = 0;
424
425             int ret = Interop.MediaCodec.GetSupportedType(_handle, codecType, isEncoder, out value);
426
427             MultimediaDebug.AssertNoError(ret);
428
429             return (MediaCodecTypes)value;
430         }
431
432         private bool CheckMimeType(Type type, int value)
433         {
434             int codecType = value & CodecTypeMask;
435
436             if (!Enum.IsDefined(type, value))
437             {
438                 throw new ArgumentException($"The mime type value is invalid : { value }.");
439             }
440
441             return Enum.IsDefined(typeof(SupportedCodecType), codecType);
442         }
443
444         #region OutputAvailable event
445         private EventHandler<OutputAvailableEventArgs> _outputAvailable;
446         private Interop.MediaCodec.OutputBufferAvailableCallback _outputBufferAvailableCb;
447         private object _outputAvailableLock = new object();
448
449         /// <summary>
450         /// Occurs when an output buffer is available.
451         /// </summary>
452         /// <remarks>The output packet needs to be disposed after it is used to clean up unmanaged resources.</remarks>
453         public event EventHandler<OutputAvailableEventArgs> OutputAvailable
454         {
455             add
456             {
457                 ValidateNotDisposed();
458
459                 lock (_outputAvailableLock)
460                 {
461                     if (_outputAvailable == null)
462                     {
463                         RegisterOutputAvailableCallback();
464                     }
465                     _outputAvailable += value;
466                 }
467             }
468             remove
469             {
470                 ValidateNotDisposed();
471
472                 lock (_outputAvailableLock)
473                 {
474                     _outputAvailable -= value;
475                     if (_outputAvailable == null)
476                     {
477                         // We can remove handler first, because we know the method that unregisters callback does not throw.
478                         UnregisterOutputAvailableCallback();
479                     }
480                 }
481             }
482         }
483
484         private void RegisterOutputAvailableCallback()
485         {
486             _outputBufferAvailableCb = (packetHandle, _) =>
487             {
488                 if (_outputAvailable == null)
489                 {
490                     Interop.MediaPacket.Destroy(packetHandle);
491                     return;
492                 }
493
494                 OutputAvailableEventArgs args = null;
495                 try
496                 {
497                     args = new OutputAvailableEventArgs(packetHandle);
498                 }
499                 catch (Exception e)
500                 {
501                     Interop.MediaPacket.Destroy(packetHandle);
502
503                     MultimediaLog.Error(typeof(MediaCodec).FullName, "Failed to raise OutputAvailable event", e);
504                 }
505
506                 if (args != null)
507                 {
508                     _outputAvailable?.Invoke(this, args);
509                 }
510             };
511
512             int ret = Interop.MediaCodec.SetOutputBufferAvailableCb(_handle, _outputBufferAvailableCb);
513
514             MultimediaDebug.AssertNoError(ret);
515         }
516
517         private void UnregisterOutputAvailableCallback()
518         {
519             int ret = Interop.MediaCodec.UnsetOutputBufferAvailableCb(_handle);
520
521             MultimediaDebug.AssertNoError(ret);
522         }
523         #endregion
524
525         #region InputProcessed event
526         private Interop.MediaCodec.InputBufferUsedCallback _inputBufferUsedCb;
527
528         /// <summary>
529         /// Occurs when an input packet is processed.
530         /// </summary>
531         /// <see cref="ProcessInput(MediaPacket)"/>
532         public event EventHandler<InputProcessedEventArgs> InputProcessed;
533
534         private void RegisterInputProcessed()
535         {
536             _inputBufferUsedCb = (lockedPacketHandle, _) =>
537             {
538                 MediaPacket packet = null;
539
540                 // Lock must be disposed here, note that the packet won't be disposed.
541                 using (MediaPacket.Lock packetLock =
542                     MediaPacket.Lock.FromHandle(lockedPacketHandle))
543                 {
544                     Debug.Assert(packetLock != null);
545
546                     packet = packetLock.MediaPacket;
547                 }
548                 Debug.Assert(packet != null);
549
550                 InputProcessed?.Invoke(this, new InputProcessedEventArgs(packet));
551             };
552
553             int ret = Interop.MediaCodec.SetInputBufferUsedCb(_handle, _inputBufferUsedCb);
554
555             MultimediaDebug.AssertNoError(ret);
556         }
557         #endregion
558
559         #region ErrorOccurred event
560         private Interop.MediaCodec.ErrorCallback _errorCb;
561
562         /// <summary>
563         /// Occurs whenever an error is produced in the codec.
564         /// </summary>
565         public event EventHandler<MediaCodecErrorOccurredEventArgs> ErrorOccurred;
566
567         private void RegisterErrorOccurred()
568         {
569             _errorCb = (errorCode, _) =>
570             {
571                 MediaCodecError error = (Enum.IsDefined(typeof(MediaCodecError), errorCode)) ?
572                     (MediaCodecError)errorCode : MediaCodecError.InternalError;
573
574                 ErrorOccurred?.Invoke(this, new MediaCodecErrorOccurredEventArgs(error));
575             };
576             int ret = Interop.MediaCodec.SetErrorCb(_handle, _errorCb);
577
578             MultimediaDebug.AssertNoError(ret);
579         }
580         #endregion
581
582         #region EosReached event
583         private Interop.MediaCodec.EosCallback _eosCb;
584
585         /// <summary>
586         /// Occurs when the codec processes all input data.
587         /// </summary>
588         public event EventHandler<EventArgs> EosReached;
589
590         private void RegisterEosReached()
591         {
592             _eosCb = _ => EosReached?.Invoke(this, EventArgs.Empty);
593
594             int ret = Interop.MediaCodec.SetEosCb(_handle, _eosCb);
595
596             MultimediaDebug.AssertNoError(ret);
597         }
598
599         #endregion
600
601         #region BufferStatusChanged event
602         private Interop.MediaCodec.BufferStatusCallback _bufferStatusCb;
603
604         /// <summary>
605         /// Occurs when the codec needs more data or has enough data.
606         /// </summary>
607         public event EventHandler<BufferStatusChangedEventArgs> BufferStatusChanged;
608
609         private void RegisterBufferStatusChanged()
610         {
611             _bufferStatusCb = (statusCode, _) =>
612             {
613                 Debug.Assert(Enum.IsDefined(typeof(MediaCodecStatus), statusCode),
614                     $"{ statusCode } is not defined in MediaCodecStatus!");
615
616                 BufferStatusChanged?.Invoke(this,
617                     new BufferStatusChangedEventArgs((MediaCodecStatus)statusCode));
618             };
619
620             int ret = Interop.MediaCodec.SetBufferStatusCb(_handle, _bufferStatusCb);
621
622             MultimediaDebug.AssertNoError(ret);
623         }
624         #endregion
625     }
626 }