Release 4.0.0-preview1-00051
[platform/core/csapi/tizenfx.git] / src / Tizen.Multimedia.Recorder / Recorder / Recorder.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 Native = Interop.Recorder;
21
22 namespace Tizen.Multimedia
23 {
24     static internal class RecorderLog
25     {
26         internal const string Tag = "Tizen.Multimedia.Recorder";
27     }
28
29     /// <summary>
30     /// The recorder class provides methods to create audio/video recorder,
31     ///  to start, stop and save the recorded content. It also provides methods
32     ///  to get/set various attributes and capabilities of recorder.
33     /// </summary>
34     public class Recorder : IDisposable
35     {
36         private IntPtr _handle = IntPtr.Zero;
37         private bool _disposed = false;
38         private RecorderState _state = RecorderState.None;
39
40         /// <summary>
41         /// Audio recorder constructor.
42         /// </summary>
43         public Recorder()
44         {
45             RecorderErrorFactory.ThrowIfError(Native.Create(out _handle),
46                 "Failed to create Audio recorder");
47
48             Feature = new RecorderFeatures(this);
49             Setting = new RecorderSettings(this);
50
51             RegisterCallbacks();
52
53             SetState(RecorderState.Created);
54         }
55
56         /// <summary>
57         /// Video recorder constructor.
58         /// </summary>
59         /// <param name="camera">
60         /// The camera object.
61         /// </param>
62         public Recorder(Camera camera)
63         {
64             RecorderErrorFactory.ThrowIfError(Native.CreateVideo(camera.Handle, out _handle),
65                 "Failed to create Video recorder.");
66
67             Feature = new RecorderFeatures(this);
68             Setting = new RecorderSettings(this);
69
70             RegisterCallbacks();
71
72             SetState(RecorderState.Created);
73         }
74
75         /// <summary>
76         /// Recorder destructor.
77         /// </summary>
78         ~Recorder()
79         {
80             Dispose (false);
81         }
82
83         internal IntPtr GetHandle()
84         {
85             ValidateNotDisposed();
86             return _handle;
87         }
88
89         #region Dispose support
90         /// <summary>
91         /// Releases the unmanaged resources used by the Recorder.
92         /// </summary>
93         /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
94         protected virtual void Dispose(bool disposing)
95         {
96             if (!_disposed)
97             {
98                 if (disposing)
99                 {
100                     // to be used if there are any other disposable objects
101                 }
102                 if (_handle != IntPtr.Zero)
103                 {
104                     Native.Destroy(_handle);
105                     _handle = IntPtr.Zero;
106                 }
107                 _disposed = true;
108             }
109         }
110
111         /// <summary>
112         /// Releases all resources used by the Recorder.
113         /// </summary>
114         /// <since_tizen> 3 </since_tizen>
115         public void Dispose()
116         {
117             Dispose(true);
118             GC.SuppressFinalize(this);
119         }
120
121         internal void ValidateNotDisposed()
122         {
123             if (_disposed)
124             {
125                 throw new ObjectDisposedException(nameof(Recorder));
126             }
127         }
128         #endregion Dispose support
129
130         #region Check recorder state
131         internal void ValidateState(params RecorderState[] required)
132         {
133             ValidateNotDisposed();
134
135             Debug.Assert(required.Length > 0);
136
137             var curState = _state;
138             if (!required.Contains(curState))
139             {
140                 throw new InvalidOperationException($"The recorder is not in a valid state. " +
141                     $"Current State : { curState }, Valid State : { string.Join(", ", required) }.");
142             }
143         }
144
145         internal void SetState(RecorderState state)
146         {
147             _state = state;
148         }
149         #endregion Check recorder state
150
151         #region EventHandlers
152         /// <summary>
153         /// Event that occurs when an error occurs during recorder operation.
154         /// </summary>
155         /// <since_tizen> 3 </since_tizen>
156         public event EventHandler<RecordingErrorOccurredEventArgs> ErrorOccurred;
157         private Native.RecorderErrorCallback _errorOccuredCallback;
158
159         /// <summary>
160         /// Event that occurs when recorder is interrupted.
161         /// </summary>
162         /// <since_tizen> 3 </since_tizen>
163         public event EventHandler<RecorderInterruptedEventArgs> Interrupted;
164         private Native.InterruptedCallback _interruptedCallback;
165
166         /// <summary>
167         /// This event occurs when recorder state is changed.
168         /// </summary>
169         /// <since_tizen> 3 </since_tizen>
170         public event EventHandler<RecorderStateChangedEventArgs> StateChanged;
171         private Native.StatechangedCallback _stateChangedCallback;
172
173         /// <summary>
174         /// Event that occurs when recording information changes.
175         /// </summary>
176         /// <since_tizen> 3 </since_tizen>
177         public event EventHandler<RecordingProgressEventArgs> RecordingProgress;
178         private Native.RecordingProgressCallback _recordingProgressCallback;
179
180         /// <summary>
181         /// Event that occurs when audio stream data is being delivered.
182         /// </summary>
183         /// <since_tizen> 3 </since_tizen>
184         public event EventHandler<AudioStreamDeliveredEventArgs> AudioStreamDelivered;
185         private Native.AudioStreamCallback _audioStreamCallback;
186
187         /// <summary>
188         /// Event that occurs when recording limit is reached.
189         /// </summary>
190         /// <since_tizen> 3 </since_tizen>
191         public event EventHandler<RecordingLimitReachedEventArgs> RecordingLimitReached;
192         private Native.RecordingLimitReachedCallback _recordingLimitReachedCallback;
193
194         /// <summary>
195         /// Event that occurs when muxed stream data is being delivered.
196         /// </summary>
197         /// <since_tizen> 3 </since_tizen>
198         public event EventHandler<MuxedStreamDeliveredEventArgs> MuxedStreamDelivered;
199         private Native.MuxedStreamCallback _muxedStreamCallback;
200         #endregion EventHandlers
201
202         #region Properties
203         /// <summary>
204         /// Gets the various recorder features.
205         /// </summary>
206         /// <since_tizen> 3 </since_tizen>
207         public RecorderFeatures Feature { get; }
208
209         /// <summary>
210         /// Get/Set the various recorder settings.
211         /// </summary>
212         /// <since_tizen> 3 </since_tizen>
213         public RecorderSettings Setting { get; }
214
215         /// <summary>
216         /// The current state of the recorder.
217         /// </summary>
218         /// <since_tizen> 3 </since_tizen>
219         /// <value>A <see cref="RecorderState"/> that specifies the state of recorder.</value>
220         /// <exception cref="ObjectDisposedException">The camera already has been disposed.</exception>
221         public RecorderState State
222         {
223             get
224             {
225                 ValidateNotDisposed();
226
227                 RecorderState val = 0;
228
229                 RecorderErrorFactory.ThrowIfError(Native.GetState(_handle, out val),
230                     "Failed to get recorder state.");
231
232                 return val;
233             }
234         }
235         #endregion Properties
236
237         #region Methods
238         /// <summary>
239         /// Prepare the media recorder for recording.
240         /// The recorder must be in the <see cref="RecorderState.Created"/> state.
241         /// After this method is finished without any exception,
242         /// The state of recorder will be changed to <see cref="RecorderState.Ready"/> state.
243         /// </summary>
244         /// <since_tizen> 3 </since_tizen>
245         /// <remarks>
246         /// Before calling the function, it is required to set AudioEncoder,
247         /// videoencoder and fileformat properties of recorder.
248         /// </remarks>
249         /// <exception cref="InvalidOperationException">In case of any invalid operations.</exception>
250         /// <exception cref="ObjectDisposedException">The camera already has been disposed.</exception>
251         public void Prepare()
252         {
253             ValidateState(RecorderState.Created);
254
255             RecorderErrorFactory.ThrowIfError(Native.Prepare(_handle),
256                 "Failed to prepare media recorder for recording");
257
258             SetState(RecorderState.Ready);
259         }
260
261         /// <summary>
262         /// Resets the media recorder.
263         /// The recorder must be in the <see cref="RecorderState.Ready"/> state.
264         /// After this method is finished without any exception,
265         /// The state of recorder will be changed to <see cref="RecorderState.Created"/> state.
266         /// </summary>
267         /// <since_tizen> 3 </since_tizen>
268         /// <exception cref="InvalidOperationException">In case of any invalid operations.</exception>
269         /// <exception cref="ObjectDisposedException">The camera already has been disposed.</exception>
270         public void Unprepare()
271         {
272             ValidateState(RecorderState.Ready);
273
274             RecorderErrorFactory.ThrowIfError(Native.Unprepare(_handle),
275                 "Failed to reset the media recorder");
276
277             SetState(RecorderState.Created);
278         }
279
280         /// <summary>
281         /// Starts the recording.
282         /// The recorder must be in the <see cref="RecorderState.Ready"/> state.
283         /// After this method is finished without any exception,
284         /// The state of recorder will be changed to <see cref="RecorderState.Recording"/> state.
285         /// </summary>
286         /// <since_tizen> 3 </since_tizen>
287         /// <remarks>
288         /// If file path has been set to an existing file, this file is removed automatically and updated by new one.
289         /// In the video recorder, some preview format does not support record mode. It will return InvalidOperation error.
290         ///     You should use default preview format or CameraPixelFormatNv12 in the record mode.
291         ///     The filename should be set before this function is invoked.
292         /// </remarks>
293         /// <privilege>
294         /// http://tizen.org/privilege/recorder
295         /// </privilege>
296         /// <exception cref="InvalidOperationException">In case of any invalid operations.</exception>
297         /// <exception cref="ObjectDisposedException">The camera already has been disposed.</exception>
298         /// <exception cref="UnauthorizedAccessException">In case of access to the resources cannot be granted.</exception>
299         public void Start()
300         {
301             ValidateState(RecorderState.Ready, RecorderState.Paused);
302
303             RecorderErrorFactory.ThrowIfError(Native.Start(_handle),
304                 "Failed to start the media recorder");
305
306             SetState(RecorderState.Recording);
307         }
308
309         /// <summary>
310         /// Pause the recording.
311         /// The recorder must be in the <see cref="RecorderState.Recording"/> state.
312         /// After this method is finished without any exception,
313         /// The state of recorder will be changed to <see cref="RecorderState.Paused"/> state.
314         /// </summary>
315         /// <since_tizen> 3 </since_tizen>
316         /// <remarks>
317         /// Recording can be resumed with Start().
318         /// </remarks>
319         /// <privilege>
320         /// http://tizen.org/privilege/recorder
321         /// </privilege>
322         /// <exception cref="InvalidOperationException">In case of any invalid operations.</exception>
323         /// <exception cref="ObjectDisposedException">The camera already has been disposed.</exception>
324         /// <exception cref="UnauthorizedAccessException">In case of access to the resources cannot be granted.</exception>
325         public void Pause()
326         {
327             ValidateState(RecorderState.Recording);
328
329             RecorderErrorFactory.ThrowIfError(Native.Pause(_handle),
330                 "Failed to pause the media recorder");
331
332             SetState(RecorderState.Paused);
333         }
334
335         /// <summary>
336         /// Stops recording and saves the result.
337         /// The recorder must be in the <see cref="RecorderState.Recording"/> or <see cref="RecorderState.Paused"/> state.
338         /// After this method is finished without any exception,
339         /// The state of recorder will be changed to <see cref="RecorderState.Ready"/> state.
340         /// </summary>
341         /// <since_tizen> 3 </since_tizen>
342         /// <privilege>
343         /// http://tizen.org/privilege/recorder
344         /// </privilege>
345         /// <exception cref="InvalidOperationException">In case of any invalid operations.</exception>
346         /// <exception cref="ObjectDisposedException">The camera already has been disposed.</exception>
347         /// <exception cref="UnauthorizedAccessException">In case of access to the resources cannot be granted.</exception>
348         public void Commit()
349         {
350             ValidateState(RecorderState.Recording, RecorderState.Paused);
351
352             RecorderErrorFactory.ThrowIfError(Native.Commit(_handle),
353                 "Failed to save the recorded content");
354
355             SetState(RecorderState.Ready);
356         }
357
358         /// <summary>
359         /// Cancels the recording.
360         /// The recording data is discarded and not written in the recording file.
361         /// The recorder must be in the <see cref="RecorderState.Recording"/> or <see cref="RecorderState.Paused"/> state.
362         /// After this method is finished without any exception,
363         /// The state of recorder will be changed to <see cref="RecorderState.Ready"/> state.
364         /// </summary>
365         /// <since_tizen> 3 </since_tizen>
366         /// <privilege>
367         /// http://tizen.org/privilege/recorder
368         /// </privilege>
369         /// <exception cref="InvalidOperationException">In case of any invalid operations.</exception>
370         /// <exception cref="ObjectDisposedException">The camera already has been disposed.</exception>
371         /// <exception cref="UnauthorizedAccessException">In case of access to the resources cannot be granted.</exception>
372         public void Cancel()
373         {
374             ValidateState(RecorderState.Recording, RecorderState.Paused);
375
376             RecorderErrorFactory.ThrowIfError(Native.Cancel(_handle),
377                 "Failed to cancel the recording");
378
379             SetState(RecorderState.Ready);
380         }
381
382         /// <summary>
383         /// Sets the audio stream policy.
384         /// </summary>
385         /// <since_tizen> 3 </since_tizen>
386         /// <param name="policy">Policy.</param>
387         /// <exception cref="ObjectDisposedException">The camera already has been disposed.</exception>
388         public void SetAudioStreamPolicy(AudioStreamPolicy policy)
389         {
390             ValidateNotDisposed();
391
392             RecorderErrorFactory.ThrowIfError(Native.SetAudioStreamPolicy(_handle, policy.Handle),
393                 "Failed to set audio stream policy");
394         }
395         #endregion Methods
396
397         #region Callback registrations
398         private void RegisterCallbacks()
399         {
400             RegisterErrorCallback();
401             RegisterInterruptedCallback();
402             RegisterStateChangedCallback();
403             RegisterRecordingProgressCallback();
404             RegisterAudioStreamDeliveredCallback();
405             RegisterRecordingLimitReachedEvent();
406             RegisterMuxedStreamEvent();
407         }
408
409         private void RegisterErrorCallback()
410         {
411             _errorOccuredCallback = (RecorderErrorCode error, RecorderState current, IntPtr userData) =>
412             {
413                 ErrorOccurred?.Invoke(this, new RecordingErrorOccurredEventArgs(error, current));
414             };
415             RecorderErrorFactory.ThrowIfError(Native.SetErrorCallback(_handle, _errorOccuredCallback, IntPtr.Zero),
416                 "Setting Error callback failed");
417         }
418
419         private void RegisterInterruptedCallback()
420         {
421             _interruptedCallback = (RecorderPolicy policy, RecorderState previous, RecorderState current, IntPtr userData) =>
422             {
423                 Interrupted?.Invoke(this, new RecorderInterruptedEventArgs(policy, previous, current));
424             };
425             RecorderErrorFactory.ThrowIfError(Native.SetInterruptedCallback(_handle, _interruptedCallback, IntPtr.Zero),
426                 "Setting Interrupted callback failed");
427         }
428
429         private void RegisterStateChangedCallback()
430         {
431             _stateChangedCallback = (RecorderState previous, RecorderState current, bool byPolicy, IntPtr userData) =>
432             {
433                 SetState(current);
434                 Log.Info(RecorderLog.Tag, "Recorder state changed " + previous.ToString() + " -> " + current.ToString());
435                 StateChanged?.Invoke(this, new RecorderStateChangedEventArgs(previous, current, byPolicy));
436             };
437             RecorderErrorFactory.ThrowIfError(Native.SetStateChangedCallback(_handle, _stateChangedCallback, IntPtr.Zero),
438                 "Setting state changed callback failed");
439         }
440
441         private void RegisterRecordingProgressCallback()
442         {
443             _recordingProgressCallback = (ulong elapsedTime, ulong fileSize, IntPtr userData) =>
444             {
445                 RecordingProgress?.Invoke(this, new RecordingProgressEventArgs(elapsedTime, fileSize));
446             };
447             RecorderErrorFactory.ThrowIfError(Native.SetRecordingProgressCallback(_handle, _recordingProgressCallback, IntPtr.Zero),
448                 "Setting status changed callback failed");
449         }
450
451         private void RegisterAudioStreamDeliveredCallback()
452         {
453             _audioStreamCallback = (IntPtr stream, int streamSize, AudioSampleType type, int channel, uint recordingTime, IntPtr userData) =>
454             {
455                 AudioStreamDelivered?.Invoke(this, new AudioStreamDeliveredEventArgs(stream, streamSize, type, channel, recordingTime));
456             };
457             RecorderErrorFactory.ThrowIfError(Native.SetAudioStreamCallback(_handle, _audioStreamCallback, IntPtr.Zero),
458                 "Setting audiostream callback failed");
459         }
460
461         private void RegisterRecordingLimitReachedEvent()
462         {
463             _recordingLimitReachedCallback = (RecordingLimitType type, IntPtr userData) =>
464             {
465                 RecordingLimitReached?.Invoke(this, new RecordingLimitReachedEventArgs(type));
466             };
467             RecorderErrorFactory.ThrowIfError(Native.SetLimitReachedCallback(_handle, _recordingLimitReachedCallback, IntPtr.Zero),
468                 "Setting limit reached callback failed");
469         }
470
471         private void RegisterMuxedStreamEvent()
472         {
473             _muxedStreamCallback = (IntPtr stream, int streamSize, ulong offset, IntPtr userData) =>
474             {
475                 MuxedStreamDelivered?.Invoke(this, new MuxedStreamDeliveredEventArgs(stream, streamSize, offset));
476             };
477             RecorderErrorFactory.ThrowIfError(Native.SetMuxedStreamCallback(_handle, _muxedStreamCallback, IntPtr.Zero),
478                 "Setting muxed stream callback failed");
479         }
480         #endregion Callback registrations
481     }
482 }