2 * Copyright (c) 2016 Samsung Electronics Co., Ltd All Rights Reserved
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 using System.Diagnostics;
20 using System.Threading;
21 using Native = Interop.Recorder;
22 using NativeHandle = Interop.RecorderHandle;
24 namespace Tizen.Multimedia
27 /// Recorder is a base class for audio and video recorders that
28 /// provides the ability to control the recording of a multimedia content.\n
30 /// Simple audio and audio/video are supported.
32 public abstract partial class Recorder : IDisposable
34 private readonly NativeHandle _handle;
35 private RecorderState _state;
36 private ThreadLocal<bool> _isInAudioStreamStoring = new ThreadLocal<bool>();
38 internal Recorder(NativeHandle handle)
55 internal NativeHandle Handle
61 throw new ObjectDisposedException(nameof(Recorder));
68 #region Dispose support
69 private bool _disposed;
72 /// Releases the unmanaged resources used by the Recorder.
80 /// Releases the resources used by the Recorder.
82 /// <param name="disposing">
83 /// true to release both managed and unmanaged resources; false to release only unmanaged resources.
85 protected virtual void Dispose(bool disposing)
97 #endregion Dispose support
99 #region State validation
100 internal void ValidateState(params RecorderState[] required)
102 Debug.Assert(required.Length > 0);
104 var curState = _state;
105 if (!required.Contains(curState))
107 throw new InvalidOperationException("The recorder is not in a valid state. " +
108 $"Current State : { curState }, Valid State : { string.Join(", ", required) }.");
112 private void SetState(RecorderState state)
121 /// Gets the current state of the recorder.
123 /// <value>A <see cref="RecorderState"/> that specifies the state of recorder.</value>
124 /// <exception cref="ObjectDisposedException">The recorder already has been disposed of.</exception>
125 public RecorderState State
129 Native.GetState(Handle, out var val).ThrowIfError("Failed to get recorder state.");
134 #endregion Properties
138 /// Prepare the media recorder for recording.
141 /// The recorder should be in the <see cref="RecorderState.Idle"/> state.\n
142 /// The state of the recorder will be the <see cref="RecorderState.Ready"/> after this.\n
143 /// It has no effect if the current state is the <see cref="RecorderState.Ready"/>.
145 /// <exception cref="InvalidOperationException">
146 /// The recorder is not in the valid state.\n
148 /// An internal error occurred.
150 /// <exception cref="ObjectDisposedException">The recorder already has been disposed of.</exception>
151 public void Prepare()
153 if (_state == RecorderState.Ready)
158 ValidateState(RecorderState.Idle);
160 Native.Prepare(Handle).ThrowIfError("Failed to prepare media recorder");
162 SetState(RecorderState.Ready);
165 private void ThrowIfAccessedInAudioStreamStoring()
167 if (_isInAudioStreamStoring.Value)
169 throw new InvalidOperationException("The method can't be called in the AudioStreamStoring event");
174 /// Resets the media recorder.
177 /// The recorder should be in the <see cref="RecorderState.Ready"/> state.
178 /// The state of recorder will be <see cref="RecorderState.Idle"/> after this.
179 /// It has no effect if the current state is the <see cref="RecorderState.Idle"/>.
181 /// <exception cref="InvalidOperationException">
182 /// The recorder is not in the valid state.\n
184 /// An internal error occurred.
186 /// <exception cref="ObjectDisposedException">The recorder already has been disposed of.</exception>
187 public void Unprepare()
189 ThrowIfAccessedInAudioStreamStoring();
191 if (_state == RecorderState.Idle)
196 ValidateState(RecorderState.Ready);
198 Native.Unprepare(Handle).ThrowIfError("Failed to reset the media recorder");
200 SetState(RecorderState.Idle);
204 /// Starts the recording.
207 /// The recorder must be in the <see cref="RecorderState.Ready"/> state.
208 /// The state of recorder will be <see cref="RecorderState.Recording"/> after this. \n
210 /// If the specified path exists, the file is removed automatically and updated by new one.\n
211 /// The mediastorage privilege(http://tizen.org/privilege/mediastorage) is required if the path is relevant to media storage.\n
212 /// The externalstorage privilege(http://tizen.org/privilege/externalstorage) is required if the path is relevant to external storage.\n
214 /// In the video recorder, some preview format does not support record mode.
215 /// You should use default preview format or <see cref="CameraPixelFormat.Nv12"/> in the record mode.
217 /// <param name="savePath">The file path for recording result.</param>
218 /// <privilege>http://tizen.org/privilege/recorder</privilege>
219 /// <exception cref="InvalidOperationException">
220 /// The recorder is not in the valid state.\n
222 /// The preview format of the camera is not supported.
224 /// An internal error occurred.
226 /// <exception cref="ObjectDisposedException">The recorder already has been disposed of.</exception>
227 /// <exception cref="ArgumentNullException"><paramref name="savePath"/> is null.</exception>
228 /// <exception cref="ArgumentException"><paramref name="savePath"/> is a zero-length string, contains only white space.</exception>
229 /// <exception cref="UnauthorizedAccessException">Caller does not have required privilege.</exception>
230 /// <seealso cref="Commit"/>
231 /// <seealso cref="Cancel"/>
232 public void Start(string savePath)
234 ValidateState(RecorderState.Ready);
236 if (savePath == null)
238 throw new ArgumentNullException(nameof(savePath));
241 if (string.IsNullOrWhiteSpace(savePath))
243 throw new ArgumentException($"{nameof(savePath)} is an empty string.", nameof(savePath));
246 Native.SetFileName(Handle, savePath).ThrowIfError("Failed to set save path.");
248 Native.Start(Handle).ThrowIfError("Failed to start the media recorder");
250 SetState(RecorderState.Recording);
254 /// Resumes the recording.
257 /// The recorder should be in the <see cref="RecorderState.Paused"/> state.
258 /// The state of recorder will be <see cref="RecorderState.Recording"/> after this.
259 /// It has no effect if the current state is the <see cref="RecorderState.Recording"/>.
261 /// <exception cref="InvalidOperationException">
262 /// The recorder is not in the valid state.\n
264 /// An internal error occurred.
266 /// <exception cref="ObjectDisposedException">The recorder already has been disposed of.</exception>
269 if (_state == RecorderState.Recording)
274 ValidateState(RecorderState.Paused);
276 Native.Start(Handle).ThrowIfError("Failed to resume the media recorder");
278 SetState(RecorderState.Recording);
282 /// Pause the recording.
285 /// The recorder should be in the <see cref="RecorderState.Recording"/> state.
286 /// The state of recorder will be <see cref="RecorderState.Paused"/> after this.
287 /// It has no effect if the current state is the <see cref="RecorderState.Paused"/>.
289 /// <exception cref="InvalidOperationException">
290 /// The recorder is not in the valid state.\n
292 /// An internal error occurred.
294 /// <exception cref="ObjectDisposedException">The recorder already has been disposed of.</exception>
297 if (_state == RecorderState.Paused)
302 ValidateState(RecorderState.Recording);
304 Native.Pause(Handle).ThrowIfError("Failed to pause the media recorder");
306 SetState(RecorderState.Paused);
310 /// Stops recording and saves the result.
313 /// The recorder must be in the <see cref="RecorderState.Recording"/> or <see cref="RecorderState.Paused"/> state.
314 /// The state of recorder will be <see cref="RecorderState.Ready"/> after the operation.
316 /// <exception cref="InvalidOperationException">
317 /// The recorder is not in the valid state.\n
319 /// The method is called in <see cref="AudioStreamStoring"/> event.
321 /// An internal error occurred.
323 /// <exception cref="ObjectDisposedException">The recorder already has been disposed of.</exception>
326 ThrowIfAccessedInAudioStreamStoring();
328 ValidateState(RecorderState.Recording, RecorderState.Paused);
330 Native.Commit(Handle).ThrowIfError("Failed to save the recorded content");
332 SetState(RecorderState.Ready);
336 /// Cancels the recording.\n
337 /// The recording data is discarded and not written in the recording file.
340 /// The recorder must be in the <see cref="RecorderState.Recording"/> or <see cref="RecorderState.Paused"/> state.
341 /// The state of recorder will be <see cref="RecorderState.Ready"/> after the operation.
343 /// <exception cref="InvalidOperationException">
344 /// The recorder is not in the valid state.\n
346 /// The method is called in <see cref="AudioStreamStoring"/> event.
348 /// An internal error occurred.
350 /// <exception cref="ObjectDisposedException">The recorder already has been disposed of.</exception>
353 ThrowIfAccessedInAudioStreamStoring();
355 ValidateState(RecorderState.Recording, RecorderState.Paused);
357 Native.Cancel(Handle).ThrowIfError("Failed to cancel the recording");
359 SetState(RecorderState.Ready);
363 /// Apply the audio stream policy.
366 /// The recorder must be in the <see cref="RecorderState.Idle"/> or <see cref="RecorderState.Ready"/> state.
368 /// <param name="policy">The policy to apply.</param>
369 /// <exception cref="ArgumentNullException"><paramref name="policy"/> is null.</exception>
370 /// <exception cref="InvalidOperationException">
371 /// The recorder is not in the valid state.\n
373 /// <paramref name="policy"/> is not supported for the recorder.
375 /// An internal error occurred.
377 /// <exception cref="ObjectDisposedException">
378 /// The recorder already has been disposed of.\n
380 /// <paramref name="policy"/> already has been disposed of.
382 public void ApplyAudioStreamPolicy(AudioStreamPolicy policy)
386 throw new ArgumentNullException(nameof(policy));
389 ValidateState(RecorderState.Idle, RecorderState.Ready);
391 Native.SetAudioStreamPolicy(Handle, policy.Handle).ThrowIfError("Failed to apply the audio stream policy.");
395 /// Returns the peak audio input level in dB since the last call to this method.
398 /// 0dB indicates maximum input level, -300dB indicates minimum input level.\n
400 /// The recorder must be in the <see cref="RecorderState.Recording"/> or <see cref="RecorderState.Paused"/> state.
402 /// <exception cref="ObjectDisposedException">The recorder already has been disposed of.</exception>
403 public double GetPeakAudioLevel()
405 ValidateState(RecorderState.Recording, RecorderState.Paused);
407 Native.GetAudioLevel(Handle, out var level).ThrowIfError("Failed to get audio level.");
413 /// Returns the state of recorder device.
415 /// <exception cref="ArgumentException"><paramref name="type"/> is invalid.</exception>
416 public static RecorderDeviceState GetDeviceState(RecorderType type)
418 ValidationUtil.ValidateEnum(typeof(RecorderType), type, nameof(type));
420 Native.GetDeviceState(type, out var state).ThrowIfError("Failed to get device state");