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