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