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