Fix a SupportedCodecs issue causing application abortion
[platform/core/csapi/tizenfx.git] / src / Tizen.Multimedia / 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 video and audio data.
25     /// </summary>
26     public class MediaCodec : IDisposable
27     {
28         private static readonly int CodecTypeMask = 0xFFFF;
29         private static readonly int CodecKindMask = 0x3000;
30         private static readonly int CodecKindAudio = 0x1000;
31         private static readonly int CodecKindVideo = 0x2000;
32
33         private IntPtr _handle;
34
35         /// <summary>
36         /// Initialize 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             MediaCodecDebug.AssertNoError(ret);
48         }
49
50         #region IDisposable-support
51         private bool _isDisposed = false;
52
53         protected virtual void Dispose(bool disposing)
54         {
55             if (!_isDisposed)
56             {
57                 if (_handle != IntPtr.Zero)
58                 {
59                     Interop.MediaCodec.Destroy(_handle);
60                     _handle = IntPtr.Zero;
61                 }
62
63                 _isDisposed = true;
64             }
65         }
66
67         ~MediaCodec()
68         {
69             Dispose(false);
70         }
71
72         public void Dispose()
73         {
74             Dispose(true);
75
76             GC.SuppressFinalize(this);
77         }
78         #endregion
79
80         /// <summary>
81         /// Validates if the object already has been disposed of.
82         /// </summary>
83         /// <exception cref="ObjectDisposedException">The current object has been disposed of.</exception>
84         private void ValidateNotDisposed()
85         {
86             if (_isDisposed)
87             {
88                 throw new ObjectDisposedException(nameof(MediaCodec));
89             }
90         }
91
92         private static IEnumerable<MediaFormatVideoMimeType> _supportedVideoCodecs;
93
94         /// <summary>
95         /// Gets the audio codec list that the current device supports.
96         /// </summary>
97         public static IEnumerable<MediaFormatVideoMimeType> SupportedVideoCodecs
98         {
99             get
100             {
101                 if (_supportedVideoCodecs == null)
102                 {
103                     LoadSupportedCodec();
104                 }
105
106                 return _supportedVideoCodecs;
107             }
108         }
109
110         private static IEnumerable<MediaFormatAudioMimeType> _supportedAudioCodecs;
111
112
113         /// <summary>
114         /// Gets the audio codec list that the current device supports.
115         /// </summary>
116         public static IEnumerable<MediaFormatAudioMimeType> SupportedAudioCodecs
117         {
118             get
119             {
120                 if (_supportedAudioCodecs == null)
121                 {
122                     LoadSupportedCodec();
123                 }
124
125                 return _supportedAudioCodecs;
126             }
127         }
128
129         private static bool TryGetMimeTypeFromCodecType<T>(int codecType, ref T result)
130         {
131             if (codecType == -1)
132             {
133                 return false;
134             }
135
136             foreach (T value in Enum.GetValues(typeof(T)))
137             {
138                 if ((Convert.ToInt32(value) & CodecTypeMask) == codecType)
139                 {
140                     result = value;
141                     return true;
142                 }
143             }
144
145             Debug.Fail($"Unknown codec : { codecType }.");
146             return false;
147         }
148
149         private static void LoadSupportedCodec()
150         {
151             var videoCodecList = new List<MediaFormatVideoMimeType>();
152             var audioCodecList = new List<MediaFormatAudioMimeType>();
153
154             Interop.MediaCodec.SupportedCodecCallback cb = (int codecType, IntPtr arg) =>
155             {
156                 if ((codecType & CodecKindMask) == CodecKindVideo)
157                 {
158                     MediaFormatVideoMimeType mimeType = 0;
159                     if (TryGetMimeTypeFromCodecType(codecType, ref mimeType))
160                     {
161                         videoCodecList.Add(mimeType);
162                     }
163                 }
164                 else
165                 {
166                     MediaFormatAudioMimeType mimeType = 0;
167                     if (TryGetMimeTypeFromCodecType(codecType, ref mimeType))
168                     {
169                         audioCodecList.Add(mimeType);
170                     }
171                 }
172
173                 return true;
174             };
175
176             int ret = Interop.MediaCodec.ForeachSupportedCodec(cb, IntPtr.Zero);
177
178             MediaCodecDebug.AssertNoError(ret);
179
180             _supportedVideoCodecs = videoCodecList.AsReadOnly();
181             _supportedAudioCodecs = audioCodecList.AsReadOnly();
182         }
183
184         /// <summary>
185         /// Prepares the MediaCodec for encoding or decoding.
186         /// </summary>
187         /// <exception cref="InvalidOperationException">
188         ///     The codec is not configured, yet.
189         ///     <para>-or-</para>
190         ///     Internal error.
191         /// </exception>
192         public void Prepare()
193         {
194             ValidateNotDisposed();
195
196             int ret = Interop.MediaCodec.Prepare(_handle);
197
198             if (ret == (int)MediaCodecErrorCode.NotInitialized)
199             {
200                 throw new InvalidOperationException("The codec is not configured.");
201             }
202             if (ret != (int)MediaCodecErrorCode.None)
203             {
204                 throw new InvalidOperationException("Operation failed.");
205             }
206
207             MediaCodecDebug.AssertNoError(ret);
208         }
209
210         /// <summary>
211         /// Unprepares the MediaCodec.
212         /// </summary>
213         public void Unprepare()
214         {
215             ValidateNotDisposed();
216
217             int ret = Interop.MediaCodec.Unprepare(_handle);
218
219             MediaCodecDebug.AssertNoError(ret);
220         }
221
222         /// <summary>
223         /// Configures the MediaCodec.
224         /// </summary>
225         /// <param name="format">The <see cref="MediaFormat"/> for properties of media data to decode or encode.</param>
226         /// <param name="encoder">The value indicating whether the codec works as a encoder or a decoder.</param>
227         /// <param name="codecType">The value indicating whether the codec uses hardware acceleration.</param>
228         /// <exception cref="ArgumentNullException">format is null</exception>
229         /// <exception cref="ArgumentException">
230         ///     codecType is invalid.
231         ///     <para>-or-</para>
232         ///     format is neither audio type or video type.
233         ///     </exception>
234         /// <exception cref="NotSupportedException">the mime type of the format is not supported.</exception>
235         /// <see cref="SupportedAudioCodecs"/>
236         /// <see cref="SupportedVideoCodecs"/>
237         public void Configure(MediaFormat format, bool encoder, MediaCodecTypes codecType)
238         {
239             ValidateNotDisposed();
240
241             if (format == null)
242             {
243                 throw new ArgumentNullException(nameof(format));
244             }
245
246             if (codecType != MediaCodecTypes.Hardware && codecType != MediaCodecTypes.Software)
247             {
248                 throw new ArgumentException("codecType is invalid.");
249             }
250
251             if (format.Type == MediaFormatType.Audio)
252             {
253                 ConfigureAudio((AudioMediaFormat)format, encoder, codecType);
254             }
255             else if (format.Type == MediaFormatType.Video)
256             {
257                 ConfigureVideo((VideoMediaFormat)format, encoder, codecType);
258             }
259             else
260             {
261                 throw new ArgumentException("Only video and audio formats are allowed.");
262             }
263         }
264
265         private void ConfigureAudio(AudioMediaFormat format, bool encoder,
266             MediaCodecTypes supportType)
267         {
268             int codecType = (int)format.MimeType & CodecTypeMask;
269
270             if (!Enum.IsDefined(typeof(SupportedCodecType), codecType))
271             {
272                 throw new NotSupportedException("The format is not supported " +
273                     $"mime type : { Enum.GetName(typeof(MediaFormatAudioMimeType), format.MimeType) }");
274             }
275
276             DoConfigure(codecType, encoder, supportType);
277
278             if (encoder)
279             {
280                 int ret = Interop.MediaCodec.SetAudioEncoderInfo(_handle, format.SampleRate,
281                     format.Channel, format.Bit, format.BitRate);
282
283                 MediaCodecDebug.AssertNoError(ret);
284             }
285             else
286             {
287                 int ret = Interop.MediaCodec.SetAudioDecoderInfo(_handle, format.SampleRate,
288                     format.Channel, format.Bit);
289
290                 MediaCodecDebug.AssertNoError(ret);
291             }
292         }
293
294         private void ConfigureVideo(VideoMediaFormat format, bool encoder,
295             MediaCodecTypes supportType)
296         {
297             int codecType = (int)format.MimeType & CodecTypeMask;
298
299             if (!Enum.IsDefined(typeof(SupportedCodecType), codecType))
300             {
301                 throw new NotSupportedException("The format is not supported." +
302                     $"mime type : { Enum.GetName(typeof(MediaFormatVideoMimeType), format.MimeType) }");
303             }
304
305             DoConfigure(codecType, encoder, supportType);
306
307             if (encoder)
308             {
309                 int ret = Interop.MediaCodec.SetVideoEncoderInfo(_handle, format.Width,
310                     format.Height, format.FrameRate, format.BitRate / 1000);
311
312                 MediaCodecDebug.AssertNoError(ret);
313             }
314             else
315             {
316                 int ret = Interop.MediaCodec.SetVideoDecoderInfo(_handle, format.Width, format.Height);
317
318                 MediaCodecDebug.AssertNoError(ret);
319             }
320         }
321
322         private void DoConfigure(int codecType, bool encoder, MediaCodecTypes supportType)
323         {
324             Debug.Assert(Enum.IsDefined(typeof(SupportedCodecType), codecType));
325
326             int flags = (int)(encoder ? MediaCodecCodingType.Encoder : MediaCodecCodingType.Decoder);
327
328             flags |= (int)supportType;
329
330             int ret = Interop.MediaCodec.Configure(_handle, codecType, flags);
331
332             if (ret == (int)MediaCodecErrorCode.NotSupportedOnDevice)
333             {
334                 throw new NotSupportedException("The format is not supported.");
335             }
336             MediaCodecDebug.AssertNoError(ret);
337         }
338
339         /// <summary>
340         /// Add the packet to the internal queue of the codec.
341         /// </summary>
342         /// <param name="packet">The packet to be encoded or decoded.</param>
343         /// <exception cref="ArgumentNullException">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 be failed until the <see cref="InputProcessed"/> event for the packet is invoked.</remarks>
346         public void ProcessInput(MediaPacket packet)
347         {
348             ValidateNotDisposed();
349
350             if (packet == null)
351             {
352                 throw new ArgumentNullException(nameof(packet));
353             }
354
355             MediaPacket.Lock packetLock = new MediaPacket.Lock(packet);
356
357             int ret = Interop.MediaCodec.Process(_handle, packetLock.GetHandle(), 0);
358
359             if (ret == (int)MediaCodecErrorCode.InvalidState)
360             {
361                 throw new InvalidOperationException("The codec is in invalid state.");
362             }
363
364             MediaCodecDebug.AssertNoError(ret);
365         }
366
367         /// <summary>
368         /// Flush both input and output buffers.
369         /// </summary>
370         public void FlushBuffers()
371         {
372             ValidateNotDisposed();
373
374             int ret = Interop.MediaCodec.FlushBuffers(_handle);
375
376             MediaCodecDebug.AssertNoError(ret);
377         }
378
379         /// <summary>
380         /// Retrieves supported codec types for the specified params.
381         /// </summary>
382         /// <param name="encoder">The value indicating encoder or decoder.</param>
383         /// <param name="type">The mime type to query.</param>
384         /// <returns>The values indicating which codec types are supported on the current device.</returns>
385         /// <exception cref="ArgumentException">type is invalid.</exception>
386         public MediaCodecTypes GetCodecType(bool encoder, MediaFormatVideoMimeType type)
387         {
388             ValidateNotDisposed();
389
390             if (CheckMimeType(typeof(MediaFormatVideoMimeType), (int)type) == false)
391             {
392                 return 0;
393             }
394
395             return GetCodecType((int)type, encoder);
396         }
397
398         /// <summary>
399         /// Retrieves supported codec types for the specified params.
400         /// </summary>
401         /// <param name="encoder">The value indicating encoder or decoder.</param>
402         /// <param name="type">The mime type to query.</param>
403         /// <returns>The values indicating which codec types are supported on the current device.</returns>
404         /// <exception cref="ArgumentException">type is invalid.</exception>
405         public MediaCodecTypes GetCodecType(bool encoder, MediaFormatAudioMimeType type)
406         {
407             ValidateNotDisposed();
408
409             if (CheckMimeType(typeof(MediaFormatAudioMimeType), (int)type) == false)
410             {
411                 return 0;
412             }
413
414             return GetCodecType((int)type, encoder);
415         }
416
417         private MediaCodecTypes GetCodecType(int mimeType, bool isEncoder)
418         {
419             int codecType = mimeType & CodecTypeMask;
420             int value = 0;
421
422             int ret = Interop.MediaCodec.GetSupportedType(_handle, codecType, isEncoder, out value);
423
424             MediaCodecDebug.AssertNoError(ret);
425
426             return (MediaCodecTypes)value;
427         }
428
429         private bool CheckMimeType(Type type, int value)
430         {
431             int codecType = value & CodecTypeMask;
432
433             if (!Enum.IsDefined(type, value))
434             {
435                 throw new ArgumentException($"The mime type value is invalid : { value }.");
436             }
437
438             return Enum.IsDefined(typeof(SupportedCodecType), codecType);
439         }
440
441         #region OutputAvailable event
442         private EventHandler<OutputAvailableEventArgs> _outputAvailable;
443         private Interop.MediaCodec.OutputBufferAvailableCallback _outputBufferAvailableCb;
444
445         /// <summary>
446         /// Occurs when an output buffer is available.
447         /// </summary>
448         /// <remarks>The output packet needs to be disposed after it is used to clean up unmanaged resources.</remarks>
449         public event EventHandler<OutputAvailableEventArgs> OutputAvailable
450         {
451             add
452             {
453                 ValidateNotDisposed();
454
455                 if (_outputAvailable == null)
456                 {
457                     RegisterOutputAvailableCallback();
458                 }
459                 _outputAvailable += value;
460
461             }
462             remove
463             {
464                 ValidateNotDisposed();
465
466                 _outputAvailable -= value;
467                 if (_outputAvailable == null)
468                 {
469                     UnregisterOutputAvailableCallback();
470                 }
471             }
472         }
473
474         private void RegisterOutputAvailableCallback()
475         {
476             _outputBufferAvailableCb = (IntPtr packetHandle, IntPtr arg) =>
477             {
478                 OutputAvailableEventArgs args = null;
479
480                 try
481                 {
482                     args = new OutputAvailableEventArgs(packetHandle);
483                 }
484                 catch (Exception)
485                 {
486                     Interop.MediaPacket.Destroy(packetHandle);
487
488                     // TODO should we throw it to unmanaged code?
489                     throw;
490                 }
491
492                 //TODO dispose if no event handler registered
493                 _outputAvailable?.Invoke(this, args);
494             };
495
496             int ret = Interop.MediaCodec.SetOutputBufferAvaiableCb(_handle,
497                 _outputBufferAvailableCb, IntPtr.Zero);
498
499             MediaCodecDebug.AssertNoError(ret);
500         }
501
502         private void UnregisterOutputAvailableCallback()
503         {
504             int ret = Interop.MediaCodec.UnsetOutputBufferAvaiableCb(_handle);
505
506             MediaCodecDebug.AssertNoError(ret);
507         }
508         #endregion
509
510         #region InputProcessed event
511         private EventHandler<InputProcessedEventArgs> _inputProcessed;
512         private Interop.MediaCodec.InputBufferUsedCallback _inputBufferUsedCb;
513
514         /// <summary>
515         /// Occurs when an input packet is processed.
516         /// </summary>
517         /// <see cref="ProcessInput(MediaPacket)"/>
518         public event EventHandler<InputProcessedEventArgs> InputProcessed
519         {
520             add
521             {
522                 ValidateNotDisposed();
523
524                 if (_inputProcessed == null)
525                 {
526                     RegisterInputProcessed();
527                 }
528                 _inputProcessed += value;
529
530             }
531             remove
532             {
533                 ValidateNotDisposed();
534
535                 _inputProcessed -= value;
536                 if (_inputProcessed == null)
537                 {
538                     UnregisterInputProcessed();
539                 }
540             }
541         }
542
543         private void RegisterInputProcessed()
544         {
545             _inputBufferUsedCb = (IntPtr lockedPacketHandle, IntPtr arg) =>
546             {
547                 MediaPacket packet = null;
548
549                 // Lock must be disposed here, note that the packet won't be disposed.
550                 using (MediaPacket.Lock packetLock =
551                     MediaPacket.Lock.FromHandle(lockedPacketHandle))
552                 {
553                     packet = packetLock.MediaPacket;
554                 }
555                 Debug.Assert(packet != null);
556
557                 _inputProcessed?.Invoke(this, new InputProcessedEventArgs(packet));
558             };
559
560             int ret = Interop.MediaCodec.SetInputBufferUsedCb(_handle,
561                 _inputBufferUsedCb, IntPtr.Zero);
562
563             MediaCodecDebug.AssertNoError(ret);
564         }
565
566         private void UnregisterInputProcessed()
567         {
568             int ret = Interop.MediaCodec.UnsetInputBufferUsedCb(_handle);
569
570             MediaCodecDebug.AssertNoError(ret);
571         }
572         #endregion
573
574         #region ErrorOccurred event
575         private EventHandler<ErrorOccurredEventArgs> _errorOccurred;
576         private Interop.MediaCodec.ErrorCallback _errorCb;
577
578         /// <summary>
579         /// Occurs whenever an error is produced in the codec.
580         /// </summary>
581         public event EventHandler<ErrorOccurredEventArgs> ErrorOccurred
582         {
583             add
584             {
585                 ValidateNotDisposed();
586
587                 if (_errorOccurred == null)
588                 {
589                     RegisterErrorOccurred();
590                 }
591                 _errorOccurred += value;
592
593             }
594             remove
595             {
596                 ValidateNotDisposed();
597
598                 _errorOccurred -= value;
599                 if (_errorOccurred == null)
600                 {
601                     UnregisterErrorOccurred();
602                 }
603             }
604         }
605
606         private void RegisterErrorOccurred()
607         {
608             _errorCb = (int errorCode, IntPtr arg) =>
609             {
610                 MediaCodecError error = (Enum.IsDefined(typeof(MediaCodecError), errorCode)) ?
611                     (MediaCodecError)errorCode : MediaCodecError.InternalError;
612
613                 _errorOccurred?.Invoke(this, new ErrorOccurredEventArgs(error));
614             };
615             int ret = Interop.MediaCodec.SetErrorCb(_handle, _errorCb, IntPtr.Zero);
616
617             MediaCodecDebug.AssertNoError(ret);
618         }
619
620         private void UnregisterErrorOccurred()
621         {
622             int ret = Interop.MediaCodec.UnsetErrorCb(_handle);
623
624             MediaCodecDebug.AssertNoError(ret);
625         }
626         #endregion
627
628         #region EosReached event
629         private EventHandler<EosReachedEventArgs> _eosReached;
630         private Interop.MediaCodec.EosCallback _eosCb;
631
632         /// <summary>
633         /// Occurs when the codec processes all input data.
634         /// </summary>
635         public event EventHandler<EosReachedEventArgs> EosReached
636         {
637             add
638             {
639                 ValidateNotDisposed();
640
641                 if (_eosReached == null)
642                 {
643                     RegisterEosReached();
644                 }
645                 _eosReached += value;
646
647             }
648             remove
649             {
650                 ValidateNotDisposed();
651
652                 _eosReached -= value;
653                 if (_eosReached == null)
654                 {
655                     UnregisterEosReached();
656                 }
657             }
658         }
659
660         private void RegisterEosReached()
661         {
662             _eosCb = (IntPtr arg) => _eosReached?.Invoke(this, new EosReachedEventArgs());
663
664             int ret = Interop.MediaCodec.SetEosCb(_handle, _eosCb, IntPtr.Zero);
665
666             MediaCodecDebug.AssertNoError(ret);
667         }
668
669         private void UnregisterEosReached()
670         {
671             int ret = Interop.MediaCodec.UnsetEosCb(_handle);
672
673             MediaCodecDebug.AssertNoError(ret);
674         }
675         #endregion
676
677         #region BufferStatusChanged event
678         private EventHandler<BufferStatusChangedEventArgs> _bufferStatusChanged;
679         private Interop.MediaCodec.BufferStatusCallback _bufferStatusCb;
680
681         /// <summary>
682         /// Occurs when the codec needs more data or has enough data.
683         /// </summary>
684         public event EventHandler<BufferStatusChangedEventArgs> BufferStatusChanged
685         {
686             add
687             {
688                 ValidateNotDisposed();
689
690                 if (_bufferStatusChanged == null)
691                 {
692                     RegisterBufferStatusChanged();
693                 }
694                 _bufferStatusChanged += value;
695
696             }
697             remove
698             {
699                 ValidateNotDisposed();
700
701                 _bufferStatusChanged -= value;
702                 if (_bufferStatusChanged == null)
703                 {
704                     UnregisterBufferStatusChanged();
705                 }
706             }
707         }
708
709         private void RegisterBufferStatusChanged()
710         {
711             _bufferStatusCb = (int statusCode, IntPtr arg) =>
712             {
713                 Debug.Assert(Enum.IsDefined(typeof(MediaCodecStatus), statusCode),
714                     $"{ statusCode } is not defined in MediaCodecStatus!");
715
716                 _bufferStatusChanged?.Invoke(this,
717                     new BufferStatusChangedEventArgs((MediaCodecStatus)statusCode));
718             };
719
720             int ret = Interop.MediaCodec.SetBufferStatusCb(_handle, _bufferStatusCb, IntPtr.Zero);
721
722             MediaCodecDebug.AssertNoError(ret);
723         }
724
725         private void UnregisterBufferStatusChanged()
726         {
727             int ret = Interop.MediaCodec.UnsetBufferStatusCb(_handle);
728
729             MediaCodecDebug.AssertNoError(ret);
730         }
731         #endregion
732     }
733 }