/* * Copyright (c) 2016 Samsung Electronics Co., Ltd All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the License); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an AS IS BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using System; using System.Collections.Generic; using System.Runtime.InteropServices; using Tizen.Internals.Errors; using Native = Interop.StreamRecorder; namespace Tizen.Multimedia { static internal class StreamRecorderLog { internal const string Tag = "Tizen.Multimedia.StreamRecorder"; } /// /// Provides methods to control stream recorder. /// /// /// StreamRecorder class provides functions to record raw image frame /// also provides recording start, stop and save the content etc. /// public class StreamRecorder : IDisposable { private IntPtr _handle; private bool _disposed = false; /// /// Occurred when recording is progressing for recording status. /// private EventHandler _recordingStatusChanged; private Native.RecordingStatusCallback _recordingStatusCallback; /// /// Occurred when recording time or size reach limit. /// private EventHandler _recordingLimitReached; private Native.RecordingLimitReachedCallback _recordingLimitReachedCallback; /// /// Occurred when streamrecorder complete to use pushed buffer. /// private EventHandler _bufferConsumed; private Native.BufferConsumedCallback _bufferConsumedCallback; /// /// Occurred when streamrecorder state is changed. /// private EventHandler _recorderNotified; private Native.NotifiedCallback _notifiedCallback; /// /// Occurred when error is occured. /// private EventHandler _recordingErrorOccurred; private Native.RecorderErrorCallback _recorderErrorCallback; private List _formats; private List _audioCodec; private List _videoCodec; private List _resolutions; StreamRecorderVideoResolution _videoResolution = null; /// /// Stream recorder constructor. /// public StreamRecorder() { int ret = Native.Create(out _handle); if (ret != (int)StreamRecorderError.None) { StreamRecorderErrorFactory.ThrowException(ret, "Failed to create stream recorder"); } _formats = new List(); _audioCodec = new List(); _videoCodec = new List(); _resolutions = new List(); _videoResolution = new StreamRecorderVideoResolution(_handle); } /// /// Stream recorder destructor. /// ~StreamRecorder() { Dispose(false); } /// /// Event that occurs when streamrecorder state is changed. /// public event EventHandler RecorderNotified { add { if (_recorderNotified == null) { RegisterStreamRecorderNotifiedEvent(); } _recorderNotified += value; } remove { _recorderNotified -= value; if (_recorderNotified == null) { UnregisterStreamRecorderNotifiedEvent(); } } } /// /// Event that occurs when buffer had comsumed completely. /// public event EventHandler BufferConsumed { add { if (_bufferConsumed == null) { RegisterBufferComsumedEvent(); } _bufferConsumed += value; } remove { _bufferConsumed -= value; if (_bufferConsumed == null) { UnregisterBufferComsumedEvent(); } } } /// /// Event that occurs when recording status changed. /// public event EventHandler RecordingStatusChanged { add { if (_recordingStatusChanged == null) { RegisterRecordingStatusChangedEvent(); } _recordingStatusChanged += value; } remove { _recordingStatusChanged -= value; if (_recordingStatusChanged == null) { UnregisterRecordingStatusChangedEvent(); } } } /// /// Event that occurs when recording limit is reached. /// public event EventHandler RecordingLimitReached { add { if (_recordingLimitReached == null) { RegisterRecordingLimitReachedEvent(); } _recordingLimitReached += value; } remove { _recordingLimitReached -= value; if (_recordingLimitReached == null) { UnregisterRecordingLimitReachedEvent(); } } } /// /// Event that occurs when an error occured during recorder operation. /// public event EventHandler RecordingErrorOccurred { add { if (_recordingErrorOccurred == null) { RegisterRecordingErrorOccurredEvent(); } _recordingErrorOccurred += value; } remove { _recordingErrorOccurred -= value; if (_recordingErrorOccurred == null) { UnregisterRecordingErrorOccurredEvent(); } } } /// /// The file path to record. /// /// /// If the same file already exists in the file system, then old file /// will be overwritten. /// public string FilePath { get { IntPtr val; int ret = Native.GetFileName(_handle, out val); if ((StreamRecorderError)ret != StreamRecorderError.None) { Log.Error(StreamRecorderLog.Tag, "Failed to get filepath, " + (StreamRecorderError)ret); } string result = Marshal.PtrToStringAnsi(val); LibcSupport.Free(val); return result; } set { int ret = Native.SetFileName(_handle, value); if ((StreamRecorderError)ret != StreamRecorderError.None) { Log.Error(StreamRecorderLog.Tag, "Failed to set filepath, " + (StreamRecorderError)ret); StreamRecorderErrorFactory.ThrowException(ret, "Failed to set filepath"); } } } /// /// Get the current state of the stream recorder. /// /// The current state of stream recorder. public StreamRecorderState State { get { int val = 0; int ret = Native.GetState(_handle, out val); if ((StreamRecorderError)ret != StreamRecorderError.None) { Log.Error(StreamRecorderLog.Tag, "Failed to get stream recorder state, " + (StreamRecorderError)ret); } return (StreamRecorderState)val; } } /// /// Get/Set the file format for recording media stream. /// /// /// Must set . /// The recorder state must be state. /// /// The format does not valid. /// public StreamRecorderFileFormat FileFormat { get { int val = 0; int ret = Native.GetFileFormat(_handle, out val); if ((StreamRecorderError)ret != StreamRecorderError.None) { Log.Error(StreamRecorderLog.Tag, "Failed to get file format, " + (StreamRecorderError)ret); } return (StreamRecorderFileFormat)val; } set { int ret = Native.SetFileFormat(_handle, (int)value); if ((StreamRecorderError)ret != StreamRecorderError.None) { Log.Error(StreamRecorderLog.Tag, "Failed to set file format, " + (StreamRecorderError)ret); StreamRecorderErrorFactory.ThrowException(ret); } } } /// /// The audio codec for encoding an audio stream. /// /// /// Must set or /// by /// /// The codec does not valid. /// public StreamRecorderAudioCodec AudioCodec { get { int val = 0; int ret = Native.GetAudioEncoder(_handle, out val); if ((StreamRecorderError)ret != StreamRecorderError.None) { Log.Error(StreamRecorderLog.Tag, "Failed to get audio codec, " + (StreamRecorderError)ret); } return (StreamRecorderAudioCodec)val; } set { int ret = Native.SetAudioEncoder(_handle, (int)value); if ((StreamRecorderError)ret != StreamRecorderError.None) { Log.Error(StreamRecorderLog.Tag, "Failed to set audio codec, " + (StreamRecorderError)ret); StreamRecorderErrorFactory.ThrowException(ret); } } } /// /// The video codec for encoding video stream. /// /// /// Must set or /// by /// /// The codec does not valid. /// public StreamRecorderVideoCodec VideoCodec { get { int val = 0; int ret = Native.GetVideoEncoder(_handle, out val); if ((StreamRecorderError)ret != StreamRecorderError.None) { Log.Error(StreamRecorderLog.Tag, "Failed to get video codec, " + (StreamRecorderError)ret); } return (StreamRecorderVideoCodec)val; } set { int ret = Native.SetVideoEncoder(_handle, (int)value); if ((StreamRecorderError)ret != StreamRecorderError.None) { Log.Error(StreamRecorderLog.Tag, "Failed to set video codec, " + (StreamRecorderError)ret); StreamRecorderErrorFactory.ThrowException(ret); } } } /// /// The maximum size of a recording file in KB(kilobytes). If 0, means /// unlimited recording size. /// /// /// After reaching the limitation, the data which is being recorded will /// be discarded and not written to the file. /// The recorder state must be state. /// /// The value set to below 0. /// public int SizeLimit { get { int val = 0; int ret = Native.GetRecordingLimit(_handle, 1, out val); if ((StreamRecorderError)ret != StreamRecorderError.None) { Log.Error(StreamRecorderLog.Tag, "Failed to get size limit, " + (StreamRecorderError)ret); } return val; } set { int ret = Native.SetRecordingLimit(_handle, 1, value); if ((StreamRecorderError)ret != StreamRecorderError.None) { Log.Error(StreamRecorderLog.Tag, "Failed to set sizelimit, " + (StreamRecorderError)ret); StreamRecorderErrorFactory.ThrowException(ret, "Failed to set size limit"); } } } /// /// The time limit of a recording file in Seconds. If 0, means unlimited recording /// time. /// /// /// After reaching the limitation, the data which is being recorded will /// be discarded and not written to the file. /// The recorder state must be state. /// /// The value set to below 0. /// public int TimeLimit { get { int val = 0; int ret = Native.GetRecordingLimit(_handle, 0, out val); if ((StreamRecorderError)ret != StreamRecorderError.None) { Log.Error(StreamRecorderLog.Tag, "Failed to get time limit, " + (StreamRecorderError)ret); } return val; } set { int ret = Native.SetRecordingLimit(_handle, 0, value); if ((StreamRecorderError)ret != StreamRecorderError.None) { Log.Error(StreamRecorderLog.Tag, "Failed to set timelimit, " + (StreamRecorderError)ret); StreamRecorderErrorFactory.ThrowException(ret, "Failed to set time limit"); } } } /// /// The sampling rate of an audio stream in hertz. /// /// /// The recorder state must be state. /// Must set or /// by . /// /// The value set to below 0. public int AudioSampleRate { get { int val = 0; int ret = Native.GetAudioSampleRate(_handle, out val); if ((StreamRecorderError)ret != StreamRecorderError.None) { Log.Error(StreamRecorderLog.Tag, "Failed to get audio samplerate, " + (StreamRecorderError)ret); } return val; } set { int ret = Native.SetAudioSampleRate(_handle, value); if ((StreamRecorderError)ret != StreamRecorderError.None) { Log.Error(StreamRecorderLog.Tag, "Failed to set audio samplerate, " + (StreamRecorderError)ret); StreamRecorderErrorFactory.ThrowException(ret, "Failed to set audio samplerate"); } } } /// /// The bitrate of an audio encoder in bits per second. /// /// /// The recorder state must be state. /// Must set or /// by /// /// The value set to below 0. public int AudioBitRate { get { int val = 0; int ret = Native.GetAudioEncoderBitrate(_handle, out val); if ((StreamRecorderError)ret != StreamRecorderError.None) { Log.Error(StreamRecorderLog.Tag, "Failed to get audio bitrate, " + (StreamRecorderError)ret); } return val; } set { int ret = Native.SetAudioEncoderBitrate(_handle, value); if ((StreamRecorderError)ret != StreamRecorderError.None) { Log.Error(StreamRecorderLog.Tag, "Failed to set audio bitrate, " + (StreamRecorderError)ret); StreamRecorderErrorFactory.ThrowException(ret, "Failed to set audio bitrate"); } } } /// /// The bitrate of an video encoder in bits per second. /// /// /// The recorder state must be state. /// Must set or /// by /// /// The value set to below 0. public int VideoBitRate { get { int val = 0; int ret = Native.GetVideoEncoderBitrate(_handle, out val); if ((StreamRecorderError)ret != StreamRecorderError.None) { Log.Error(StreamRecorderLog.Tag, "Failed to get video bitrate, " + (StreamRecorderError)ret); } return val; } set { int ret = Native.SetVideoEncoderBitrate(_handle, value); if ((StreamRecorderError)ret != StreamRecorderError.None) { Log.Error(StreamRecorderLog.Tag, "Failed to set video bitrate, " + (StreamRecorderError)ret); StreamRecorderErrorFactory.ThrowException(ret, "Failed to set video bitrate"); } } } /// /// The video frame rate for recording media stream. /// /// /// The recorder state must be state. /// Must set or /// by /// /// The value set to below 0. public int VideoFrameRate { get { int val = 0; int ret = Native.GetVideoFramerate(_handle, out val); if ((StreamRecorderError)ret != StreamRecorderError.None) { Log.Error(StreamRecorderLog.Tag, "Failed to get video framerate, " + (StreamRecorderError)ret); } return val; } set { int ret = Native.SetVideoFramerate(_handle, value); if ((StreamRecorderError)ret != StreamRecorderError.None) { Log.Error(StreamRecorderLog.Tag, "Failed to set video framerate, " + (StreamRecorderError)ret); StreamRecorderErrorFactory.ThrowException(ret); } } } /// /// Get or Set the video source format for recording media stream. /// /// The value set to a invalid value. /// public StreamRecorderVideoSourceFormat VideoSourceFormat { get { int val = 0; int ret = Native.GetVideoSourceFormat(_handle, out val); if ((StreamRecorderError)ret != StreamRecorderError.None) { Log.Error(StreamRecorderLog.Tag, "Failed to get video framerate, " + (StreamRecorderError)ret); } return (StreamRecorderVideoSourceFormat)val; } set { int ret = Native.SetVideoSourceFormat(_handle, (int)value); if ((StreamRecorderError)ret != StreamRecorderError.None) { Log.Error(StreamRecorderLog.Tag, "Failed to set video framerate, " + (StreamRecorderError)ret); StreamRecorderErrorFactory.ThrowException(ret); } } } /// /// The number of audio channel. /// /// /// The attribute is applied only in Created state. /// For mono recording, set channel to 1. /// For stereo recording, set channel to 2. /// The recorder state must be state. /// /// The value set to a invalid value. public int AudioChannel { get { int val = 0; int ret = Native.GetAudioChannel(_handle, out val); if ((StreamRecorderError)ret != StreamRecorderError.None) { Log.Error(StreamRecorderLog.Tag, "Failed to get audio channel, " + (StreamRecorderError)ret); } return val; } set { int ret = Native.SetAudioChannel(_handle, value); if ((StreamRecorderError)ret != StreamRecorderError.None) { Log.Error(StreamRecorderLog.Tag, "Failed to set audio channel, " + (StreamRecorderError)ret); StreamRecorderErrorFactory.ThrowException(ret, "Failed to set audio channel"); } } } /// /// Video resolution of the video recording. /// /// /// Must set or /// by /// The recorder state must be state. /// /// The value set to a invalid value. /// public StreamRecorderVideoResolution Resolution { get { return _videoResolution; } } /// /// Retrieves all the file formats supported by the stream recorder. /// /// /// It returns a list containing all the supported file /// formats by Stream recorder. /// /// public IEnumerable SupportedFileFormats { get { if (_formats.Count == 0) { Native.FileFormatCallback callback = (StreamRecorderFileFormat format, IntPtr userData) => { _formats.Add(format); return true; }; int ret = Native.FileFormats(_handle, callback, IntPtr.Zero); if (ret != (int)StreamRecorderError.None) { StreamRecorderErrorFactory.ThrowException(ret, "Failed to get the supported fileformats"); } } return _formats; } } /// /// Retrieves all the audio encoders supported by the recorder. /// /// /// It returns a list containing all the supported audio encoders /// by recorder. /// /// public IEnumerable SupportedAudioEncodings { get { if (_audioCodec.Count == 0) { Native.AudioEncoderCallback callback = (StreamRecorderAudioCodec codec, IntPtr userData) => { _audioCodec.Add(codec); return true; }; int ret = Native.AudioEncoders(_handle, callback, IntPtr.Zero); if (ret != (int)StreamRecorderError.None) { StreamRecorderErrorFactory.ThrowException(ret, "Failed to get the supported audio encoders"); } } return _audioCodec; } } /// /// Retrieves all the video encoders supported by the recorder. /// /// /// It returns a list containing all the supported video encoders /// by recorder. /// /// public IEnumerable SupportedVideoEncodings { get { if (_videoCodec.Count == 0) { Native.VideoEncoderCallback callback = (StreamRecorderVideoCodec codec, IntPtr userData) => { _videoCodec.Add(codec); return true; }; int ret = Native.VideoEncoders(_handle, callback, IntPtr.Zero); if (ret != (int)StreamRecorderError.None) { StreamRecorderErrorFactory.ThrowException(ret, "Failed to get the supported video encoders"); } } return _videoCodec; } } /// /// Retrieves all the video resolutions supported by the recorder. /// /// /// It returns videoresolution list containing the width and height of /// different resolutions supported by recorder. /// /// public IEnumerable SupportedVideoResolutions { get { if (_resolutions.Count == 0) { Native.VideoResolutionCallback callback = (int width, int height, IntPtr userData) => { StreamRecorderVideoResolution temp = new StreamRecorderVideoResolution(width, height); _resolutions.Add(temp); return true; }; int ret = Native.VideoResolution(_handle, callback, IntPtr.Zero); if (ret != (int)StreamRecorderError.None) { StreamRecorderErrorFactory.ThrowException(ret, "Failed to get the supported video resolutions"); } } return _resolutions; } } /// /// Prepare the stream recorder. /// /// /// Before calling the function, it is required to set , /// , and properties of recorder. /// /// The streamrecorder is not in the valid state. /// public void Prepare() { int ret = Native.Prepare(_handle); if (ret != (int)StreamRecorderError.None) { StreamRecorderErrorFactory.ThrowException(ret, "Failed to prepare stream recorder"); } } /// /// Resets the stream recorder. /// /// /// The recorder state must be state by , and . /// The StreamRecorder state will be . /// /// The streamrecorder is not in the valid state. /// public void Unprepare() { int ret = Native.Unprepare(_handle); if (ret != (int)StreamRecorderError.None) { StreamRecorderErrorFactory.ThrowException(ret, "Failed to reset the stream recorder"); } } /// /// Starts the recording. /// /// /// If file path has been set to an existing file, this file is removed automatically and updated by new one. /// The filename should be set before this function is invoked. /// The recorder state must be state by or /// state by . /// The filename shuild be set by /// /// The streamrecorder is not in the valid state. /// The access ot the resources can not be granted. /// /// /// /// /// public void Start() { int ret = Native.Start(_handle); if (ret != (int)StreamRecorderError.None) { StreamRecorderErrorFactory.ThrowException(ret, "Failed to start the stream recorder"); } } /// /// Pause the recording. /// /// /// Recording can be resumed with . /// /// The streamrecorder is not in the valid state. /// /// /// public void Pause() { int ret = Native.Pause(_handle); if (ret != (int)StreamRecorderError.None) { StreamRecorderErrorFactory.ThrowException(ret, "Failed to pause the stream recorder"); } } /// /// Stops recording and saves the result. /// /// /// The recorder state must be state by or /// state by /// When you want to record audio or video file, you need to add privilege according to rules below additionally. /// /// http://tizen.org/privilege/mediastorage is needed if input or output path are relevant to media storage. /// http://tizen.org/privilege/externalstorage is needed if input or output path are relevant to external storage. /// /// /// The streamrecorder is not in the valid state. /// The access ot the resources can not be granted. /// /// public void Commit() { int ret = Native.Commit(_handle); if (ret != (int)StreamRecorderError.None) { StreamRecorderErrorFactory.ThrowException(ret, "Failed to save the recorded content"); } } /// /// Cancels the recording. /// The recording data is discarded and not written in the recording file. /// /// /// public void Cancel() { int ret = Native.Cancel(_handle); if (ret != (int)StreamRecorderError.None) { StreamRecorderErrorFactory.ThrowException(ret, "Failed to cancel the recording"); } } /// /// Push stream buffer as recording raw data. /// public void PushBuffer(MediaPacket packet) { IntPtr _packet_h = packet.GetHandle(); Log.Info("Tizen.Multimedia.StreamRecorder", "PUSH stream buffer"); int ret = Native.PushStreamBuffer(_handle, _packet_h); if (ret != (int)StreamRecorderError.None) { StreamRecorderErrorFactory.ThrowException(ret, "Failed to push buffer"); } Log.Info("Tizen.Multimedia.StreamRecorder", "PUSH stream buffer END"); } /// /// Set the source type of pushed data. /// public void EnableSourceBuffer(StreamRecorderSourceType type) { int ret = Native.EnableSourceBuffer(_handle, (int)type); if (ret != (int)StreamRecorderError.None) { StreamRecorderErrorFactory.ThrowException(ret, "Failed to set EnableSourceBuffer"); } } /// /// Release any unmanaged resources used by this object. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (!_disposed) { if (disposing) { // to be used if there are any other disposable objects } if (_handle != IntPtr.Zero) { Native.Destroy(_handle); _handle = IntPtr.Zero; } _disposed = true; } } private void RegisterStreamRecorderNotifiedEvent() { _notifiedCallback = (StreamRecorderState previous, StreamRecorderState current, StreamRecorderNotify notify, IntPtr userData) => { StreamRecorderNotifiedEventArgs eventArgs = new StreamRecorderNotifiedEventArgs(previous, current, notify); _recorderNotified?.Invoke(this, eventArgs); }; int ret = Native.SetNotifiedCallback(_handle, _notifiedCallback, IntPtr.Zero); if (ret != (int)StreamRecorderError.None) { StreamRecorderErrorFactory.ThrowException(ret, "Setting notify callback failed"); } } private void UnregisterStreamRecorderNotifiedEvent() { int ret = Native.UnsetNotifiedCallback(_handle); if (ret != (int)StreamRecorderError.None) { StreamRecorderErrorFactory.ThrowException(ret, "Unsetting notify callback failed"); } } private void RegisterBufferComsumedEvent() { _bufferConsumedCallback = (IntPtr buffer, IntPtr userData) => { StreamRecordingBufferConsumedEventArgs eventArgs = new StreamRecordingBufferConsumedEventArgs(buffer); _bufferConsumed?.Invoke(this, eventArgs); }; int ret = Native.SetBufferConsumedCallback(_handle, _bufferConsumedCallback, IntPtr.Zero); if (ret != (int)StreamRecorderError.None) { StreamRecorderErrorFactory.ThrowException(ret, "Setting buffer consumed callback failed"); } } private void UnregisterBufferComsumedEvent() { int ret = Native.UnsetBufferConsumedCallback(_handle); if (ret != (int)StreamRecorderError.None) { StreamRecorderErrorFactory.ThrowException(ret, "Unsetting buffer consumed callback failed"); } } private void RegisterRecordingStatusChangedEvent() { _recordingStatusCallback = (ulong elapsedTime, ulong fileSize, IntPtr userData) => { RecordingStatusChangedEventArgs eventArgs = new RecordingStatusChangedEventArgs((long)elapsedTime, (long)fileSize); _recordingStatusChanged?.Invoke(this, eventArgs); }; int ret = Native.SetStatusChangedCallback(_handle, _recordingStatusCallback, IntPtr.Zero); if (ret != (int)StreamRecorderError.None) { StreamRecorderErrorFactory.ThrowException(ret, "Setting status changed callback failed"); } } private void UnregisterRecordingStatusChangedEvent() { int ret = Native.UnsetStatusChangedCallback(_handle); if (ret != (int)StreamRecorderError.None) { StreamRecorderErrorFactory.ThrowException(ret, "Unsetting status changed callback failed"); } } private void RegisterRecordingLimitReachedEvent() { _recordingLimitReachedCallback = (StreamRecordingLimitType type, IntPtr userData) => { StreamRecordingLimitReachedEventArgs eventArgs = new StreamRecordingLimitReachedEventArgs(type); _recordingLimitReached?.Invoke(this, eventArgs); }; int ret = Native.SetLimitReachedCallback(_handle, _recordingLimitReachedCallback, IntPtr.Zero); if (ret != (int)StreamRecorderError.None) { StreamRecorderErrorFactory.ThrowException(ret, "Setting limit reached callback failed"); } } private void UnregisterRecordingLimitReachedEvent() { int ret = Native.UnsetLimitReachedCallback(_handle); if (ret != (int)StreamRecorderError.None) { StreamRecorderErrorFactory.ThrowException(ret, "Unsetting limit reached callback failed"); } } private void RegisterRecordingErrorOccurredEvent() { _recorderErrorCallback = (StreamRecorderErrorCode error, StreamRecorderState current, IntPtr userData) => { StreamRecordingErrorOccurredEventArgs eventArgs = new StreamRecordingErrorOccurredEventArgs(error, current); _recordingErrorOccurred?.Invoke(this, eventArgs); }; int ret = Native.SetErrorCallback(_handle, _recorderErrorCallback, IntPtr.Zero); if (ret != (int)StreamRecorderError.None) { StreamRecorderErrorFactory.ThrowException(ret, "Setting Error callback failed"); } } private void UnregisterRecordingErrorOccurredEvent() { int ret = Native.UnsetErrorCallback(_handle); if (ret != (int)StreamRecorderError.None) { StreamRecorderErrorFactory.ThrowException(ret, "Unsetting Error callback failed"); } } } }