5a50f737b0480c42b7232e9e44224fc36c9eaaca
[platform/core/csapi/tizenfx.git] / src / Tizen.Multimedia / AudioManager / AudioStreamPolicy.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
20 namespace Tizen.Multimedia
21 {
22     /// <summary>
23     /// Provides the ability to control the sound stream.
24     /// </summary>
25     /// <since_tizen> 3 </since_tizen>
26     public class AudioStreamPolicy : IDisposable
27     {
28         private AudioStreamPolicyHandle _handle;
29         private bool _disposed = false;
30         private Interop.AudioStreamPolicy.FocusStateChangedCallback _focusStateChangedCallback;
31         private static AudioDevice _inputDevice = null;
32         private static AudioDevice _outputDevice = null;
33         private const string Tag = "Tizen.Multimedia.AudioStreamPolicy";
34
35         /// <summary>
36         /// Initializes a new instance of the <see cref="AudioStreamPolicy"/> class with <see cref="AudioStreamType"/>.
37         /// </summary>
38         /// <remarks>
39         /// To apply the stream policy according to this stream information, the AudioStreamPolicy should
40         /// be passed to other APIs related to playback or recording. (For example., <see cref="T:Tizen.Multimedia.Player"/>,
41         /// <see cref="T:Tizen.Multimedia.WavPlayer"/> , etc.)
42         /// </remarks>
43         /// <param name="streamType">The type of the sound stream for which the policy needs to be created.</param>
44         /// <exception cref="ArgumentException"><paramref name="streamType"/> is invalid.</exception>
45         /// <since_tizen> 3 </since_tizen>
46         public AudioStreamPolicy(AudioStreamType streamType)
47         {
48             ValidationUtil.ValidateEnum(typeof(AudioStreamType), streamType, nameof(streamType));
49
50             _focusStateChangedCallback = (IntPtr streamInfo, AudioStreamFocusOptions focusMask,
51                 AudioStreamFocusState state, AudioStreamFocusChangedReason reason, AudioStreamBehaviors behaviors,
52                 string extraInfo, IntPtr _) =>
53             {
54                 FocusStateChanged?.Invoke(this,
55                     new AudioStreamPolicyFocusStateChangedEventArgs(focusMask, state, reason, behaviors, extraInfo));
56             };
57
58             Interop.AudioStreamPolicy.Create(streamType, _focusStateChangedCallback,
59                 IntPtr.Zero, out _handle).ThrowIfError("Unable to create stream information");
60
61             Debug.Assert(_handle != null);
62         }
63
64         /// <summary>
65         /// Occurs when the state of focus that belongs to the current AudioStreamPolicy is changed.
66         /// </summary>
67         /// <remarks>
68         /// The event is raised in the internal thread.
69         /// </remarks>
70         /// <since_tizen> 4 </since_tizen>
71         public event EventHandler<AudioStreamPolicyFocusStateChangedEventArgs> FocusStateChanged;
72
73         /// <summary>
74         /// Gets the <see cref="AudioVolumeType"/>.
75         /// </summary>
76         /// <remarks>
77         /// If the <see cref="AudioStreamType"/> of the current AudioStreamPolicy is <see cref="AudioStreamType.Emergency"/>,
78         /// it returns <see cref="AudioVolumeType.None"/>.
79         /// </remarks>
80         /// <value>The <see cref="AudioVolumeType"/> of the policy instance.</value>
81         /// <exception cref="ObjectDisposedException">The <see cref="AudioStreamPolicy"/> has already been disposed of.</exception>
82         /// <since_tizen> 3 </since_tizen>
83         public AudioVolumeType VolumeType
84         {
85             get
86             {
87                 var ret = Interop.AudioStreamPolicy.GetSoundType(Handle, out var type);
88                 if (ret == AudioManagerError.NoData)
89                 {
90                     return AudioVolumeType.None;
91                 }
92
93                 ret.ThrowIfError("Failed to get volume type");
94
95                 return type;
96             }
97         }
98
99         private AudioStreamFocusState GetFocusState(bool playback)
100         {
101             int ret = Interop.AudioStreamPolicy.GetFocusState(Handle, out var stateForPlayback, out var stateForRecording);
102             MultimediaDebug.AssertNoError(ret);
103
104             return playback ? stateForPlayback : stateForRecording;
105         }
106
107         /// <summary>
108         /// Gets the state of focus for the playback.
109         /// </summary>
110         /// <value>The state of focus for playback.</value>
111         /// <exception cref="ObjectDisposedException">The <see cref="AudioStreamPolicy"/> has already been disposed of.</exception>
112         /// <since_tizen> 3 </since_tizen>
113         public AudioStreamFocusState PlaybackFocusState => GetFocusState(true);
114
115         /// <summary>
116         /// Gets the state of focus for the recording.
117         /// </summary>
118         /// <value>The state of focus for recording.</value>
119         /// <exception cref="ObjectDisposedException">The <see cref="AudioStreamPolicy"/> has already been disposed of.</exception>
120         /// <since_tizen> 3 </since_tizen>
121         public AudioStreamFocusState RecordingFocusState => GetFocusState(false);
122
123         /// <summary>
124         /// Gets or sets the auto focus reacquisition.
125         /// </summary>
126         /// <value>
127         /// true if the auto focus reacquisition is enabled; otherwise, false.<br/>
128         /// The default is true.
129         /// </value>
130         /// <remarks>
131         /// If you don't want to reacquire the focus you've lost automatically,
132         /// disable the focus reacquisition.
133         /// </remarks>
134         /// <exception cref="ObjectDisposedException">The <see cref="AudioStreamPolicy"/> has already been disposed of.</exception>
135         /// <since_tizen> 3 </since_tizen>
136         public bool FocusReacquisitionEnabled
137         {
138             get
139             {
140                 Interop.AudioStreamPolicy.GetFocusReacquisition(Handle, out var enabled).
141                     ThrowIfError("Failed to get focus reacquisition state");
142
143                 return enabled;
144             }
145             set
146             {
147                 Interop.AudioStreamPolicy.SetFocusReacquisition(Handle, value).
148                     ThrowIfError("Failed to set focus reacquisition");
149             }
150         }
151
152         internal AudioStreamPolicyHandle Handle
153         {
154             get
155             {
156                 if (_disposed)
157                 {
158                     throw new ObjectDisposedException(nameof(AudioStreamPolicy));
159                 }
160                 return _handle;
161             }
162         }
163
164         /// <summary>
165         /// Acquires the stream focus.
166         /// </summary>
167         /// <param name="options">The focuses that you want to acquire.</param>
168         /// <param name="behaviors">The requesting behaviors.</param>
169         /// <param name="extraInfo">The extra information for this request. This value can be null.</param>
170         /// <exception cref="ArgumentException"><paramref name="options"/> is zero.</exception>
171         /// <exception cref="ArgumentOutOfRangeException">
172         ///     <paramref name="options"/> contain a invalid bit.<br/>
173         ///     -or-<br/>
174         ///     <paramref name="behaviors"/> contain a invalid bit.
175         /// </exception>
176         /// <exception cref="InvalidOperationException">The focus has already been acquired.</exception>
177         /// <exception cref="AudioPolicyException">Called in <see cref="FocusStateChanged"/> raised by releasing focus.</exception>
178         /// <exception cref="ObjectDisposedException">The <see cref="AudioStreamPolicy"/> has already been disposed of.</exception>
179         /// <since_tizen> 3 </since_tizen>
180         public void AcquireFocus(AudioStreamFocusOptions options, AudioStreamBehaviors behaviors, string extraInfo)
181         {
182             if (options == 0)
183             {
184                 throw new ArgumentException("options can't be zero.", nameof(options));
185             }
186
187             if (options.IsValid() == false)
188             {
189                 throw new ArgumentOutOfRangeException(nameof(options), options, "options contains a invalid bit.");
190             }
191
192             if (behaviors.IsValid() == false)
193             {
194                 throw new ArgumentOutOfRangeException(nameof(behaviors), behaviors, "behaviors contains a invalid bit.");
195             }
196
197             Interop.AudioStreamPolicy.AcquireFocus(Handle, options, behaviors, extraInfo).
198                 ThrowIfError("Failed to acquire focus");
199         }
200
201         /// <summary>
202         /// Releases the acquired focus.
203         /// </summary>
204         /// <param name="options">The focus mask that you want to release.</param>
205         /// <param name="behaviors">The requesting behaviors.</param>
206         /// <param name="extraInfo">The extra information for this request. This value can be null.</param>
207         /// <exception cref="ArgumentException"><paramref name="options"/> is zero.</exception>
208         /// <exception cref="ArgumentOutOfRangeException">
209         ///     <paramref name="options"/> contain a invalid bit.<br/>
210         ///     -or-<br/>
211         ///     <paramref name="behaviors"/> contain a invalid bit.
212         /// </exception>
213         /// <exception cref="InvalidOperationException">The focus has not been acquired.</exception>
214         /// <exception cref="ObjectDisposedException">The <see cref="AudioStreamPolicy"/> has already been disposed of.</exception>
215         /// <since_tizen> 3 </since_tizen>
216         public void ReleaseFocus(AudioStreamFocusOptions options, AudioStreamBehaviors behaviors, string extraInfo)
217         {
218             if (options == 0)
219             {
220                 throw new ArgumentException("options can't be zero.", nameof(options));
221             }
222
223             if (options.IsValid() == false)
224             {
225                 throw new ArgumentOutOfRangeException(nameof(options), options, "options contains a invalid bit.");
226             }
227
228             if (behaviors.IsValid() == false)
229             {
230                 throw new ArgumentOutOfRangeException(nameof(behaviors), behaviors, "behaviors contains a invalid bit.");
231             }
232
233             Interop.AudioStreamPolicy.ReleaseFocus(Handle, options, behaviors, extraInfo).
234                 ThrowIfError("Failed to release focus");
235         }
236
237         /// <summary>
238         /// Applies the stream routing.
239         /// </summary>
240         /// <remarks>
241         /// If the stream has not been made yet, this will be applied when the stream starts to play.
242         /// </remarks>
243         /// <seealso cref="AddDeviceForStreamRouting(AudioDevice)"/>
244         /// <seealso cref="RemoveDeviceForStreamRouting(AudioDevice)"/>
245         /// <exception cref="ObjectDisposedException">The <see cref="AudioStreamPolicy"/> has already been disposed of.</exception>
246         /// <since_tizen> 3 </since_tizen>
247         public void ApplyStreamRouting()
248         {
249             Interop.AudioStreamPolicy.ApplyStreamRouting(Handle).ThrowIfError("Failed to apply stream routing");
250         }
251
252         /// <summary>
253         /// Adds a device for the stream routing.
254         /// </summary>
255         /// <param name="device">The device to add.</param>
256         /// <remarks>
257         /// The available <see cref="AudioStreamType"/> is <see cref="AudioStreamType.Voip"/> and <see cref="AudioStreamType.MediaExternalOnly"/>.
258         /// </remarks>
259         /// <exception cref="InvalidOperationException">
260         ///     The device is not connected.<br/>
261         ///     -or-<br/>
262         ///     An internal error occurs.
263         /// </exception>
264         /// <exception cref="ArgumentNullException"><paramref name="device"/> is null.</exception>
265         /// <exception cref="AudioPolicyException"><see cref="AudioStreamType"/> of <paramref name="device"/> is unavailable for this.</exception>
266         /// <exception cref="ObjectDisposedException">The <see cref="AudioStreamPolicy"/> has already been disposed of.</exception>
267         /// <seealso cref="AudioManager.GetConnectedDevices()"/>
268         /// <seealso cref="ApplyStreamRouting"/>
269         /// <since_tizen> 3 </since_tizen>
270         public void AddDeviceForStreamRouting(AudioDevice device)
271         {
272             if (device == null)
273             {
274                 throw new ArgumentNullException(nameof(device));
275             }
276
277             var ret = Interop.AudioStreamPolicy.AddDeviceForStreamRouting(Handle, device.Id);
278
279             if (ret == AudioManagerError.NoData)
280             {
281                 throw new InvalidOperationException("The device seems not connected.");
282             }
283
284             ret.ThrowIfError("Failed to add device for stream routing");
285         }
286
287         /// <summary>
288         /// Removes the device for the stream routing.
289         /// </summary>
290         /// <param name="device">The device to remove.</param>
291         /// <remarks>
292         /// The available <see cref="AudioStreamType"/> is <see cref="AudioStreamType.Voip"/> and <see cref="AudioStreamType.MediaExternalOnly"/>.
293         /// </remarks>
294         /// <exception cref="InvalidOperationException">An internal error occurs.</exception>
295         /// <exception cref="ArgumentNullException"><paramref name="device"/> is null.</exception>
296         /// <exception cref="ObjectDisposedException">The <see cref="AudioStreamPolicy"/> has already been disposed of.</exception>
297         /// <seealso cref="AudioManager.GetConnectedDevices()"/>
298         /// <since_tizen> 3 </since_tizen>
299         public void RemoveDeviceForStreamRouting(AudioDevice device)
300         {
301             if (device == null)
302             {
303                 throw new ArgumentNullException(nameof(device));
304             }
305
306             Interop.AudioStreamPolicy.RemoveDeviceForStreamRouting(Handle, device.Id).
307                 ThrowIfError("Failed to remove device for stream routing");
308         }
309
310         /// <summary>
311         /// Gets or sets the preferred input device.
312         /// </summary>
313         /// <value>
314         /// The <see cref="AudioDevice"/> instance.<br/>
315         /// The default is null which means any device is not set on this property.
316         /// </value>
317         /// <remarks>
318         /// This property is to set a specific built-in device when the system has multiple devices of the same built-in device type.
319         /// When there's only one device for a built-in device type in the system, nothing will happen even if this property is set successfully.
320         /// </remarks>
321         /// <exception cref="ArgumentException">A device is not for input.</exception>
322         /// <exception cref="InvalidOperationException">An internal error occurs.</exception>
323         /// <exception cref="AudioPolicyException">A device is not supported by this <see cref="AudioStreamPolicy"/> instance.</exception>
324         /// <exception cref="ObjectDisposedException">The <see cref="AudioStreamPolicy"/> has already been disposed of.</exception>
325         /// <seealso cref="AudioManager.GetConnectedDevices()"/>
326         /// <since_tizen> 6 </since_tizen>
327         public AudioDevice PreferredInputDevice
328         {
329             get
330             {
331                 /* This P/Invoke intends to validate if the core audio system
332                  * is normal. Otherwise, it'll throw an error here. */
333                 Interop.AudioStreamPolicy.GetPreferredDevice(Handle, out var inDeviceId, out _).
334                     ThrowIfError("Failed to get preferred input device");
335
336                 Log.Debug(Tag, $"preferred input device id:{inDeviceId}");
337
338                 return _inputDevice;
339             }
340             set
341             {
342                 Interop.AudioStreamPolicy.SetPreferredDevice(Handle, AudioDeviceIoDirection.Input, value?.Id ?? 0).
343                     ThrowIfError("Failed to set preferred input device");
344
345                 _inputDevice = value;
346             }
347         }
348
349         /// <summary>
350         /// Gets or sets the preferred output device.
351         /// </summary>
352         /// <value>
353         /// The <see cref="AudioDevice"/> instance.<br/>
354         /// The default is null which means any device is not set on this property.
355         /// </value>
356         /// <remarks>
357         /// This property is to set a specific built-in device when the system has multiple devices of the same built-in device type.
358         /// When there's only one device for a built-in device type in the system, nothing will happen even if this property is set successfully.
359         /// </remarks>
360         /// <exception cref="ArgumentException">A device is not for output.</exception>
361         /// <exception cref="InvalidOperationException">An internal error occurs.</exception>
362         /// <exception cref="AudioPolicyException">A device is not supported by this <see cref="AudioStreamPolicy"/> instance.</exception>
363         /// <exception cref="ObjectDisposedException">The <see cref="AudioStreamPolicy"/> has already been disposed of.</exception>
364         /// <seealso cref="AudioManager.GetConnectedDevices()"/>
365         /// <since_tizen> 6 </since_tizen>
366         public AudioDevice PreferredOutputDevice
367         {
368             get
369             {
370                 /* This P/Invoke intends to validate if the core audio system
371                  * is normal. Otherwise, it'll throw an error here. */
372                 Interop.AudioStreamPolicy.GetPreferredDevice(Handle, out _, out var outDeviceId).
373                     ThrowIfError("Failed to get preferred output device");
374
375                 Log.Debug(Tag, $"preferred output device id:{outDeviceId}");
376
377                 return _outputDevice;
378             }
379             set
380             {
381                 Interop.AudioStreamPolicy.SetPreferredDevice(Handle, AudioDeviceIoDirection.Output, value?.Id ?? 0).
382                     ThrowIfError("Failed to set preferred output device");
383
384                 _outputDevice = value;
385             }
386         }
387
388         /// <summary>
389         /// Checks if any stream from the current AudioStreamPolicy is using the device.
390         /// </summary>
391         /// <returns>true if any audio stream from the current AudioStreamPolicy is using the device; otherwise, false.</returns>
392         /// <param name="device">The device to be checked.</param>
393         /// <remarks>
394         /// The AudioStreamPolicy can be applied to each playback or recording stream via other API set.
395         /// (For example., <see cref="T:Tizen.Multimedia.Player"/>, <see cref="T:Tizen.Multimedia.WavPlayer"/>,
396         /// <see cref="T:Tizen.Multimedia.AudioPlayback"/>, <see cref="T:Tizen.Multimedia.AudioCapture"/>, etc.)
397         /// This method returns true only when the device is used for the stream which meets to the two conditions.
398         /// One is that the current AudioStreamPolicy sets a audio route path to the device and the other is that the playback
399         /// or recording stream from other API set should have already started to prepare or to play.(It depends on the API set.)
400         /// </remarks>
401         /// <exception cref="ArgumentNullException"><paramref name="device"/> is null.</exception>
402         /// <exception cref="InvalidOperationException">An internal error occurs.</exception>
403         /// <exception cref="ObjectDisposedException">The <see cref="AudioStreamPolicy"/> has already been disposed of.</exception>
404         /// <seealso cref="AudioManager.GetConnectedDevices()"/>
405         /// <since_tizen> 6 </since_tizen>
406         public bool HasStreamOnDevice(AudioDevice device)
407         {
408             if (device == null)
409             {
410                 throw new ArgumentNullException(nameof(device));
411             }
412
413             var ret = Interop.AudioStreamPolicy.IsStreamOnDevice(Handle, device.Id, out var isOn);
414             ret.ThrowIfError("Failed to check stream on device");
415
416             return isOn;
417         }
418
419         /// <summary>
420         /// Releases all resources used by the <see cref="AudioStreamPolicy"/>.
421         /// </summary>
422         /// <since_tizen> 3 </since_tizen>
423         public void Dispose()
424         {
425             Dispose(true);
426             GC.SuppressFinalize(this);
427         }
428
429         /// <summary>
430         /// Releases the unmanaged resources used by the <see cref="AudioStreamPolicy"/>.
431         /// </summary>
432         /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
433         /// <since_tizen> 3 </since_tizen>
434         protected virtual void Dispose(bool disposing)
435         {
436             if (_disposed)
437             {
438                 return;
439             }
440
441             if (disposing)
442             {
443                 if (_handle != null)
444                 {
445                     _handle.Dispose();
446                 }
447                 _disposed = true;
448             }
449         }
450
451         #region Static events
452
453         private static bool _isWatchCallbackRegistered;
454         private static EventHandler<StreamFocusStateChangedEventArgs> _streamFocusStateChanged;
455         private static Interop.AudioStreamPolicy.FocusStateWatchCallback _focusStateWatchCallback;
456         private static readonly object _streamFocusEventLock = new object();
457
458         /// <summary>
459         /// Occurs when the focus state for stream types is changed regardless of the process.
460         /// </summary>
461         /// <since_tizen> 3 </since_tizen>
462         public static event EventHandler<StreamFocusStateChangedEventArgs> StreamFocusStateChanged
463         {
464             add
465             {
466                 lock (_streamFocusEventLock)
467                 {
468                     if (_isWatchCallbackRegistered == false)
469                     {
470                         RegisterFocusStateWatch();
471                         _isWatchCallbackRegistered = true;
472                     }
473                     _streamFocusStateChanged += value;
474                 }
475             }
476             remove
477             {
478                 lock (_streamFocusEventLock)
479                 {
480                     _streamFocusStateChanged -= value;
481                 }
482             }
483         }
484
485         private static void RegisterFocusStateWatch()
486         {
487             _focusStateWatchCallback = (id, options, focusState, reason, extraInfo, _) =>
488             {
489                 _streamFocusStateChanged?.Invoke(null,
490                     new StreamFocusStateChangedEventArgs(options, focusState, reason, extraInfo));
491             };
492
493             Interop.AudioStreamPolicy.AddFocusStateWatchCallback(
494                 AudioStreamFocusOptions.Playback | AudioStreamFocusOptions.Recording,
495                 _focusStateWatchCallback, IntPtr.Zero, out var cbId).
496                 ThrowIfError("Failed to initialize focus state event");
497         }
498         #endregion
499     }
500 }