Release 4.0.0-preview1-00051
[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 video and 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         /// 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             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 already has 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 a encoder or a decoder.</param>
230         /// <param name="codecType">The value indicating whether the codec uses hardware acceleration.</param>
231         /// <exception cref="ArgumentNullException">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">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 be failed 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">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">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
448         /// <summary>
449         /// Occurs when an output buffer is available.
450         /// </summary>
451         /// <remarks>The output packet needs to be disposed after it is used to clean up unmanaged resources.</remarks>
452         public event EventHandler<OutputAvailableEventArgs> OutputAvailable
453         {
454             add
455             {
456                 ValidateNotDisposed();
457
458                 if (_outputAvailable == null)
459                 {
460                     RegisterOutputAvailableCallback();
461                 }
462                 _outputAvailable += value;
463
464             }
465             remove
466             {
467                 ValidateNotDisposed();
468
469                 _outputAvailable -= value;
470                 if (_outputAvailable == null)
471                 {
472                     UnregisterOutputAvailableCallback();
473                 }
474             }
475         }
476
477         private void RegisterOutputAvailableCallback()
478         {
479             _outputBufferAvailableCb = (packetHandle, _) =>
480             {
481                 OutputAvailableEventArgs args = null;
482
483                 try
484                 {
485                     args = new OutputAvailableEventArgs(packetHandle);
486                 }
487                 catch (Exception)
488                 {
489                     Interop.MediaPacket.Destroy(packetHandle);
490
491                     // TODO should we throw it to unmanaged code?
492                     throw;
493                 }
494
495                 //TODO dispose if no event handler registered
496                 _outputAvailable?.Invoke(this, args);
497             };
498
499             int ret = Interop.MediaCodec.SetOutputBufferAvailableCb(_handle,
500                 _outputBufferAvailableCb, IntPtr.Zero);
501
502             MultimediaDebug.AssertNoError(ret);
503         }
504
505         private void UnregisterOutputAvailableCallback()
506         {
507             int ret = Interop.MediaCodec.UnsetOutputBufferAvailableCb(_handle);
508
509             MultimediaDebug.AssertNoError(ret);
510         }
511         #endregion
512
513         #region InputProcessed event
514         private Interop.MediaCodec.InputBufferUsedCallback _inputBufferUsedCb;
515
516         /// <summary>
517         /// Occurs when an input packet is processed.
518         /// </summary>
519         /// <see cref="ProcessInput(MediaPacket)"/>
520         public event EventHandler<InputProcessedEventArgs> InputProcessed;
521
522         private void RegisterInputProcessed()
523         {
524             _inputBufferUsedCb = (lockedPacketHandle, _) =>
525             {
526                 MediaPacket packet = null;
527
528                 // Lock must be disposed here, note that the packet won't be disposed.
529                 using (MediaPacket.Lock packetLock =
530                     MediaPacket.Lock.FromHandle(lockedPacketHandle))
531                 {
532                     Debug.Assert(packetLock != null);
533
534                     packet = packetLock.MediaPacket;
535                 }
536                 Debug.Assert(packet != null);
537
538                 InputProcessed?.Invoke(this, new InputProcessedEventArgs(packet));
539             };
540
541             int ret = Interop.MediaCodec.SetInputBufferUsedCb(_handle,
542                 _inputBufferUsedCb, IntPtr.Zero);
543
544             MultimediaDebug.AssertNoError(ret);
545         }
546
547         private void UnregisterInputProcessed()
548         {
549             int ret = Interop.MediaCodec.UnsetInputBufferUsedCb(_handle);
550
551             MultimediaDebug.AssertNoError(ret);
552         }
553         #endregion
554
555         #region ErrorOccurred event
556         private Interop.MediaCodec.ErrorCallback _errorCb;
557
558         /// <summary>
559         /// Occurs whenever an error is produced in the codec.
560         /// </summary>
561         public event EventHandler<MediaCodecErrorOccurredEventArgs> ErrorOccurred;
562
563         private void RegisterErrorOccurred()
564         {
565             _errorCb = (errorCode, _) =>
566             {
567                 MediaCodecError error = (Enum.IsDefined(typeof(MediaCodecError), errorCode)) ?
568                     (MediaCodecError)errorCode : MediaCodecError.InternalError;
569
570                 ErrorOccurred?.Invoke(this, new MediaCodecErrorOccurredEventArgs(error));
571             };
572             int ret = Interop.MediaCodec.SetErrorCb(_handle, _errorCb, IntPtr.Zero);
573
574             MultimediaDebug.AssertNoError(ret);
575         }
576
577         private void UnregisterErrorOccurred()
578         {
579             int ret = Interop.MediaCodec.UnsetErrorCb(_handle);
580
581             MultimediaDebug.AssertNoError(ret);
582         }
583         #endregion
584
585         #region EosReached event
586         private EventHandler<EventArgs> _eosReached;
587         private Interop.MediaCodec.EosCallback _eosCb;
588
589         // TODO replace
590         /// <summary>
591         /// Occurs when the codec processes all input data.
592         /// </summary>
593         public event EventHandler<EventArgs> EosReached
594         {
595             add
596             {
597                 ValidateNotDisposed();
598
599                 if (_eosReached == null)
600                 {
601                     RegisterEosReached();
602                 }
603                 _eosReached += value;
604
605             }
606             remove
607             {
608                 ValidateNotDisposed();
609
610                 _eosReached -= value;
611                 if (_eosReached == null)
612                 {
613                     UnregisterEosReached();
614                 }
615             }
616         }
617
618         private void RegisterEosReached()
619         {
620             _eosCb = _ => _eosReached?.Invoke(this, EventArgs.Empty);
621
622             int ret = Interop.MediaCodec.SetEosCb(_handle, _eosCb, IntPtr.Zero);
623
624             MultimediaDebug.AssertNoError(ret);
625         }
626
627         private void UnregisterEosReached()
628         {
629             int ret = Interop.MediaCodec.UnsetEosCb(_handle);
630
631             MultimediaDebug.AssertNoError(ret);
632         }
633         #endregion
634
635         #region BufferStatusChanged event
636         private EventHandler<BufferStatusChangedEventArgs> _bufferStatusChanged;
637         private Interop.MediaCodec.BufferStatusCallback _bufferStatusCb;
638
639         /// <summary>
640         /// Occurs when the codec needs more data or has enough data.
641         /// </summary>
642         public event EventHandler<BufferStatusChangedEventArgs> BufferStatusChanged
643         {
644             add
645             {
646                 ValidateNotDisposed();
647
648                 if (_bufferStatusChanged == null)
649                 {
650                     RegisterBufferStatusChanged();
651                 }
652                 _bufferStatusChanged += value;
653
654             }
655             remove
656             {
657                 ValidateNotDisposed();
658
659                 _bufferStatusChanged -= value;
660                 if (_bufferStatusChanged == null)
661                 {
662                     UnregisterBufferStatusChanged();
663                 }
664             }
665         }
666
667         private void RegisterBufferStatusChanged()
668         {
669             _bufferStatusCb = (statusCode, _) =>
670             {
671                 Debug.Assert(Enum.IsDefined(typeof(MediaCodecStatus), statusCode),
672                     $"{ statusCode } is not defined in MediaCodecStatus!");
673
674                 _bufferStatusChanged?.Invoke(this,
675                     new BufferStatusChangedEventArgs((MediaCodecStatus)statusCode));
676             };
677
678             int ret = Interop.MediaCodec.SetBufferStatusCb(_handle, _bufferStatusCb, IntPtr.Zero);
679
680             MultimediaDebug.AssertNoError(ret);
681         }
682
683         private void UnregisterBufferStatusChanged()
684         {
685             int ret = Interop.MediaCodec.UnsetBufferStatusCb(_handle);
686
687             MultimediaDebug.AssertNoError(ret);
688         }
689         #endregion
690     }
691 }