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