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