[NUI] TCSACR-226 code change (#1032)
[platform/core/csapi/tizenfx.git] / src / Tizen.Multimedia.StreamRecorder / StreamRecorder / StreamRecorder.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.Diagnostics;
19 using System.Linq;
20 using NativeHandle = Interop.StreamRecorderHandle;
21 using Native = Interop.StreamRecorder;
22
23 namespace Tizen.Multimedia
24 {
25     /// <summary>
26     /// Provides the ability to record user buffer from application.
27     /// </summary>
28     /// <seealso cref="Recorder"/>
29     public partial class StreamRecorder : IDisposable
30     {
31         private NativeHandle _handle;
32         private bool _disposed = false;
33
34         private bool _audioEnabled;
35         private bool _videoEnabled;
36         private StreamRecorderVideoFormat _sourceFormat;
37
38         /// <summary>
39         /// Initialize a new instance of the <see cref="StreamRecorder"/> class.
40         /// </summary>
41         /// <exception cref="NotSupportedException">The feature is not supported.</exception>
42         /// <since_tizen> 3 </since_tizen>
43         public StreamRecorder()
44         {
45             try
46             {
47                 Native.Create(out _handle).ThrowIfError("Failed to create stream recorder.");
48             }
49             catch (TypeLoadException)
50             {
51                 throw new NotSupportedException("StreamRecorder is not supported.");
52             }
53
54             LoadCapabilities();
55
56             RegisterStreamRecorderNotifiedEvent();
57             RegisterBufferConsumedEvent();
58             RegisterRecordingStatusChangedEvent();
59             RegisterRecordingErrorOccurredEvent();
60             RegisterRecordingLimitReachedEvent();
61         }
62
63         internal NativeHandle Handle
64         {
65             get
66             {
67                 if (_disposed)
68                 {
69                     throw new ObjectDisposedException(nameof(StreamRecorder));
70                 }
71
72                 return _handle;
73             }
74         }
75
76         /// <summary>
77         /// Gets the current state of the stream recorder.
78         /// </summary>
79         /// <exception cref="ObjectDisposedException">The <see cref="StreamRecorder"/> has already been disposed.</exception>
80         /// <since_tizen> 3 </since_tizen>
81         public RecorderState State
82         {
83             get
84             {
85                 Native.GetState(Handle, out var val).ThrowIfError("Failed to get the stream recorder state.");
86
87                 return val;
88             }
89         }
90
91         private void ValidateState(params RecorderState[] required)
92         {
93             Debug.Assert(required.Length > 0);
94
95             var curState = State;
96             if (!required.Contains(curState))
97             {
98                 throw new InvalidOperationException($"The stream recorder is not in a valid state. " +
99                     $"Current State : { curState }, Valid State : { string.Join(", ", required) }.");
100             }
101         }
102
103         #region Operation methods
104         /// <summary>
105         /// Prepares the stream recorder with the specified options.
106         /// </summary>
107         /// <remarks>The recorder must be <see cref="RecorderState.Idle"/>.</remarks>
108         /// <param name="options">The options for recording.</param>
109         /// <exception cref="InvalidOperationException">The recorder is not in the valid state.</exception>
110         /// <exception cref="ArgumentException">Both <see cref="StreamRecorderOptions.Audio"/> and
111         ///     <see cref="StreamRecorderOptions.Video"/> are null.
112         /// </exception>
113         /// <exception cref="NotSupportedException"><paramref name="options"/> contains a value which is not supported.</exception>
114         /// <exception cref="ObjectDisposedException">The <see cref="StreamRecorder"/> has already been disposed.</exception>
115         /// <seealso cref="Unprepare"/>
116         /// <seealso cref="Start"/>
117         /// <seealso cref="StreamRecorderOptions"/>
118         /// <seealso cref="StreamRecorderAudioOptions"/>
119         /// <seealso cref="StreamRecorderVideoOptions"/>
120         /// <since_tizen> 4 </since_tizen>
121         public void Prepare(StreamRecorderOptions options)
122         {
123             if (options == null)
124             {
125                 throw new ArgumentNullException(nameof(options));
126             }
127
128             ValidateState(RecorderState.Idle);
129
130             options.Apply(this);
131
132             Native.Prepare(Handle).ThrowIfError("Failed to prepare stream recorder.");
133
134             _audioEnabled = options.Audio != null;
135             _videoEnabled = options.Video != null;
136
137             if (options.Video != null)
138             {
139                 _sourceFormat = options.Video.SourceFormat;
140             }
141         }
142
143         /// <summary>
144         /// Unprepares the stream recorder.
145         /// </summary>
146         /// <remarks>
147         /// The recorder state must be <see cref="RecorderState.Ready"/> state by
148         /// <see cref="Prepare(StreamRecorderOptions)"/>, <see cref="Cancel"/> and <see cref="Commit"/>.<br/>
149         /// The recorder state will be <see cref="RecorderState.Idle"/>.<br/>
150         /// <br/>
151         /// It has no effect if the recorder is already in the <see cref="RecorderState.Idle"/> state.
152         /// </remarks>
153         /// <exception cref="InvalidOperationException">The recorder is not in the valid state.</exception>
154         /// <exception cref="ObjectDisposedException">The <see cref="StreamRecorder"/> has already been disposed.</exception>
155         /// <seealso cref="Prepare"/>
156         /// <since_tizen> 3 </since_tizen>
157         public void Unprepare()
158         {
159             if (State == RecorderState.Idle)
160             {
161                 return;
162             }
163
164             ValidateState(RecorderState.Ready);
165
166             Native.Unprepare(Handle).ThrowIfError("Failed to reset the stream recorder.");
167         }
168
169         /// <summary>
170         /// Starts recording.
171         /// </summary>
172         /// <remarks>
173         /// The recorder state must be <see cref="RecorderState.Ready"/> state by
174         /// <see cref="Prepare(StreamRecorderOptions)"/> or
175         /// <see cref="RecorderState.Paused"/> state by <see cref="Pause"/>.<br/>
176         /// <br/>
177         /// It has no effect if the recorder is already in the <see cref="RecorderState.Recording"/> state.
178         /// </remarks>
179         /// <exception cref="InvalidOperationException">The recorder is not in the valid state.</exception>
180         /// <exception cref="UnauthorizedAccessException">The access of the resources can not be granted.</exception>
181         /// <exception cref="ObjectDisposedException">The <see cref="StreamRecorder"/> has already been disposed.</exception>
182         /// <seealso cref="Pause"/>
183         /// <seealso cref="Commit"/>
184         /// <seealso cref="Cancel"/>
185         /// <since_tizen> 3 </since_tizen>
186         public void Start()
187         {
188             if (State == RecorderState.Recording)
189             {
190                 return;
191             }
192
193             ValidateState(RecorderState.Ready, RecorderState.Paused);
194
195             Native.Start(Handle).ThrowIfError("Failed to start the stream recorder.");
196         }
197
198         /// <summary>
199         /// Pauses recording.
200         /// </summary>
201         /// <remarks>
202         /// Recording can be resumed with <see cref="Start"/>.<br/>
203         /// <br/>
204         /// The recorder state must be <see cref="RecorderState.Recording"/> state by <see cref="Start"/>.<br/>
205         /// <br/>
206         /// It has no effect if the recorder is already in the <see cref="RecorderState.Paused"/> state.
207         /// </remarks>
208         /// <exception cref="InvalidOperationException">The recorder is not in the valid state.</exception>
209         /// <exception cref="ObjectDisposedException">The <see cref="StreamRecorder"/> has already been disposed.</exception>
210         /// <seealso cref="Start"/>
211         /// <seealso cref="Commit"/>
212         /// <seealso cref="Cancel"/>
213         /// <since_tizen> 3 </since_tizen>
214         public void Pause()
215         {
216             if (State == RecorderState.Paused)
217             {
218                 return;
219             }
220
221             ValidateState(RecorderState.Recording);
222
223             Native.Pause(Handle).ThrowIfError("Failed to pause the stream recorder.");
224         }
225
226         /// <summary>
227         /// Stops recording and saves the result.
228         /// </summary>
229         /// <remarks>
230         /// The recorder state must be <see cref="RecorderState.Recording"/> state by <see cref="Start"/> or
231         /// <see cref="RecorderState.Paused"/> state by <see cref="Pause"/>.<br/>
232         /// <br/>
233         /// The recorder state will be <see cref="RecorderState.Ready"/> after commit.
234         /// <para>
235         /// http://tizen.org/privilege/mediastorage is needed if the save path are relevant to media storage.
236         /// http://tizen.org/privilege/externalstorage is needed if the save path are relevant to external storage.
237         /// </para>
238         /// </remarks>
239         /// <privilege>http://tizen.org/privilege/mediastorage</privilege>
240         /// <privilege>http://tizen.org/privilege/externalstorage</privilege>
241         /// <exception cref="InvalidOperationException">The recorder is not in the valid state.</exception>
242         /// <exception cref="UnauthorizedAccessException">The access to the resources can not be granted.</exception>
243         /// <exception cref="ObjectDisposedException">The <see cref="StreamRecorder"/> has already been disposed.</exception>
244         /// <seealso cref="Start"/>
245         /// <seealso cref="Pause"/>
246         /// <since_tizen> 3 </since_tizen>
247         public void Commit()
248         {
249             ValidateState(RecorderState.Paused, RecorderState.Recording);
250
251             Native.Commit(Handle).ThrowIfError("Failed to commit.");
252         }
253
254         /// <summary>
255         /// Cancels recording.
256         /// The recording data is discarded and not written.
257         /// </summary>
258         /// <remarks>
259         /// The recorder state must be <see cref="RecorderState.Recording"/> state by <see cref="Start"/> or
260         /// <see cref="RecorderState.Paused"/> state by <see cref="Pause"/>.
261         /// </remarks>
262         /// <exception cref="InvalidOperationException">The recorder is not in the valid state.</exception>
263         /// <exception cref="ObjectDisposedException">The <see cref="StreamRecorder"/> has already been disposed.</exception>
264         /// <seealso cref="Start"/>
265         /// <seealso cref="Pause"/>
266         /// <since_tizen> 3 </since_tizen>
267         public void Cancel()
268         {
269             ValidateState(RecorderState.Paused, RecorderState.Recording);
270
271             Native.Cancel(Handle).ThrowIfError("Failed to cancel recording.");
272         }
273
274         private static bool AreVideoTypesMatched(StreamRecorderVideoFormat videoFormat, MediaFormatVideoMimeType mimeType)
275         {
276             return (videoFormat == StreamRecorderVideoFormat.Nv12 && mimeType == MediaFormatVideoMimeType.NV12) ||
277                 (videoFormat == StreamRecorderVideoFormat.Nv21 && mimeType == MediaFormatVideoMimeType.NV21) ||
278                 (videoFormat == StreamRecorderVideoFormat.I420 && mimeType == MediaFormatVideoMimeType.I420);
279         }
280
281         /// <summary>
282         /// Pushes a packet as recording raw data.
283         /// </summary>
284         /// <param name="packet">An audio or video packet to record.</param>
285         /// <remarks>
286         /// The recorder state must be <see cref="RecorderState.Recording"/> state by <see cref="Start"/>.
287         /// </remarks>
288         /// <exception cref="InvalidOperationException">
289         ///     The recorder is not in the valid state.<br/>
290         ///     -or-<br/>
291         ///     <paramref name="packet"/> is an audio packet but audio recording is not enabled(See <see cref="StreamRecorderOptions.Audio"/>).<br/>
292         ///     -or-<br/>
293         ///     <paramref name="packet"/> is a video packet but video recording is not enabled(See <see cref="StreamRecorderOptions.Video"/>).<br/>
294         ///     -or-<br/>
295         ///     <paramref name="packet"/> is a video packet but the <see cref="VideoMediaFormat.MimeType"/> does not match the video source format.<br/>
296         ///     -or-<br/>
297         ///     An internal error occurs.
298         /// </exception>
299         /// <exception cref="ObjectDisposedException">The <see cref="StreamRecorder"/> has already been disposed.</exception>
300         /// <see cref="Prepare(StreamRecorderOptions)"/>
301         /// <seealso cref="StreamRecorderOptions.Audio"/>
302         /// <seealso cref="StreamRecorderOptions.Video"/>
303         /// <seealso cref="StreamRecorderVideoOptions.SourceFormat"/>
304         /// <since_tizen> 3 </since_tizen>
305         public void PushBuffer(MediaPacket packet)
306         {
307             if (packet == null)
308             {
309                 throw new ArgumentNullException(nameof(packet));
310             }
311
312             ValidateState(RecorderState.Recording);
313
314             switch (packet.Format.Type)
315             {
316                 case MediaFormatType.Audio:
317                     if (_audioEnabled == false)
318                     {
319                         throw new InvalidOperationException("Audio option is not set.");
320                     }
321                     break;
322
323                 case MediaFormatType.Video:
324                     if (_videoEnabled == false)
325                     {
326                         throw new InvalidOperationException("Video option is not set.");
327                     }
328
329                     if (AreVideoTypesMatched(_sourceFormat, (packet.Format as VideoMediaFormat).MimeType) == false)
330                     {
331                         throw new InvalidOperationException("Video format does not match.");
332                     }
333
334                     break;
335
336                 default:
337                     throw new ArgumentException("Packet is not valid.");
338             }
339
340             Native.PushStreamBuffer(Handle, MediaPacket.Lock.Get(packet).GetHandle())
341                 .ThrowIfError("Failed to push buffer.");
342         }
343
344         #endregion
345
346         #region Dispose support
347         /// <summary>
348         /// Release any unmanaged resources used by this object.
349         /// </summary>
350         /// <since_tizen> 3 </since_tizen>
351         public void Dispose()
352         {
353             Dispose(true);
354         }
355
356         /// <summary>
357         /// Releases the resources used by the StreamRecorder.
358         /// </summary>
359         /// <param name="disposing">
360         /// true to release both managed and unmanaged resources; false to release only unmanaged resources.
361         /// </param>
362         /// <since_tizen> 3 </since_tizen>
363         protected virtual void Dispose(bool disposing)
364         {
365             if (!_disposed)
366             {
367                 _handle?.Dispose();
368
369                 _disposed = true;
370             }
371         }
372         #endregion
373     }
374 }