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