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