| /* |
| * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. |
| * |
| * Use of this source code is governed by a BSD-style license |
| * that can be found in the LICENSE file in the root of the source |
| * tree. An additional intellectual property rights grant can be found |
| * in the file PATENTS. All contributing project authors may |
| * be found in the AUTHORS file in the root of the source tree. |
| */ |
| |
| #include "video_engine/vie_file_player.h" |
| |
| #include "modules/utility/interface/file_player.h" |
| #include "system_wrappers/interface/critical_section_wrapper.h" |
| #include "system_wrappers/interface/event_wrapper.h" |
| #include "system_wrappers/interface/thread_wrapper.h" |
| #include "system_wrappers/interface/tick_util.h" |
| #include "system_wrappers/interface/trace.h" |
| #include "video_engine/include/vie_file.h" |
| #include "video_engine/vie_input_manager.h" |
| #include "voice_engine/main/interface/voe_base.h" |
| #include "voice_engine/main/interface/voe_file.h" |
| #include "voice_engine/main/interface/voe_video_sync.h" |
| |
| namespace webrtc { |
| |
| const int kThreadWaitTimeMs = 100; |
| |
| ViEFilePlayer* ViEFilePlayer::CreateViEFilePlayer( |
| int file_id, |
| int engine_id, |
| const char* file_nameUTF8, |
| const bool loop, |
| const FileFormats file_format, |
| ViEInputManager& input_manager, |
| VoiceEngine* voe_ptr) { |
| ViEFilePlayer* self = new ViEFilePlayer(file_id, engine_id, input_manager); |
| if (!self || self->Init(file_nameUTF8, loop, file_format, voe_ptr) != 0) { |
| delete self; |
| self = NULL; |
| } |
| return self; |
| } |
| |
| ViEFilePlayer::ViEFilePlayer(int Id, |
| int engine_id, |
| ViEInputManager& input_manager) |
| : ViEFrameProviderBase(Id, engine_id), |
| play_back_started_(false), |
| input_manager_(input_manager), |
| feedback_cs_(NULL), |
| audio_cs_(NULL), |
| file_player_(NULL), |
| audio_stream_(false), |
| video_clients_(0), |
| audio_clients_(0), |
| local_audio_channel_(-1), |
| observer_(NULL), |
| voe_file_interface_(NULL), |
| voe_video_sync_(NULL), |
| decode_thread_(NULL), |
| decode_event_(NULL), |
| decoded_audio_length_(0) { |
| memset(file_name_, 0, FileWrapper::kMaxFileNameSize); |
| memset(decoded_audio_, 0, kMaxDecodedAudioLength); |
| } |
| |
| ViEFilePlayer::~ViEFilePlayer() { |
| // StopPlay deletes decode_thread_. |
| StopPlay(); |
| delete decode_event_; |
| delete audio_cs_; |
| delete feedback_cs_; |
| } |
| |
| int ViEFilePlayer::Init(const char* file_nameUTF8, |
| const bool loop, |
| const FileFormats file_format, |
| VoiceEngine* voice_engine) { |
| feedback_cs_ = CriticalSectionWrapper::CreateCriticalSection(); |
| if (!feedback_cs_) { |
| WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_, id_), |
| "ViEFilePlayer::StartPlay() failed to allocate critsect"); |
| return -1; |
| } |
| |
| audio_cs_ = CriticalSectionWrapper::CreateCriticalSection(); |
| if (!audio_cs_) { |
| WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_, id_), |
| "ViEFilePlayer::StartPlay() failed to allocate critsect"); |
| return -1; |
| } |
| |
| decode_event_ = EventWrapper::Create(); |
| if (!decode_event_) { |
| WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_, id_), |
| "ViEFilePlayer::StartPlay() failed to allocate event"); |
| return -1; |
| } |
| if (strlen(file_nameUTF8) > FileWrapper::kMaxFileNameSize) { |
| WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_, id_), |
| "ViEFilePlayer::StartPlay() Too long filename"); |
| return -1; |
| } |
| strncpy(file_name_, file_nameUTF8, strlen(file_nameUTF8) + 1); |
| |
| file_player_ = FilePlayer::CreateFilePlayer(ViEId(engine_id_, id_), |
| file_format); |
| if (!file_player_) { |
| WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_, id_), |
| "ViEFilePlayer::StartPlay() failed to create file player"); |
| return -1; |
| } |
| if (file_player_->RegisterModuleFileCallback(this) == -1) { |
| WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_, id_), |
| "ViEFilePlayer::StartPlay() failed to " |
| "RegisterModuleFileCallback"); |
| file_player_ = NULL; |
| return -1; |
| } |
| decode_thread_ = ThreadWrapper::CreateThread(FilePlayDecodeThreadFunction, |
| this, kHighestPriority, |
| "ViEFilePlayThread"); |
| if (!decode_thread_) { |
| WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_, id_), |
| "ViEFilePlayer::StartPlay() failed to start decode thread."); |
| file_player_ = NULL; |
| return -1; |
| } |
| |
| // Always try to open with Audio since we don't know on what channels the |
| // audio should be played on. |
| WebRtc_Word32 error = file_player_->StartPlayingVideoFile(file_name_, loop, |
| false); |
| if (error) { |
| // Failed to open the file with audio, try without. |
| error = file_player_->StartPlayingVideoFile(file_name_, loop, true); |
| audio_stream_ = false; |
| if (error) { |
| WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_, id_), |
| "ViEFilePlayer::StartPlay() failed to Start play video " |
| "file"); |
| return -1; |
| } |
| |
| } else { |
| audio_stream_ = true; |
| } |
| |
| if (audio_stream_) { |
| if (voice_engine) { |
| // A VoiceEngine has been provided and we want to play audio on local |
| // a channel. |
| voe_file_interface_ = VoEFile::GetInterface(voice_engine); |
| if (!voe_file_interface_) { |
| WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_, id_), |
| "ViEFilePlayer::StartPlay() failed to get VEFile " |
| "interface"); |
| return -1; |
| } |
| voe_video_sync_ = VoEVideoSync::GetInterface(voice_engine); |
| if (!voe_video_sync_) { |
| WEBRTC_TRACE(kTraceError, kTraceVideo, |
| ViEId(engine_id_, id_), |
| "ViEFilePlayer::StartPlay() failed to get " |
| "VoEVideoSync interface"); |
| return -1; |
| } |
| } |
| } |
| |
| // Read audio /(or just video) every 10ms. |
| decode_event_->StartTimer(true, 10); |
| return 0; |
| } |
| |
| int ViEFilePlayer::FrameCallbackChanged() { |
| // Starts the decode thread when someone cares. |
| if (ViEFrameProviderBase::NumberOfRegisteredFrameCallbacks() > |
| video_clients_) { |
| if (!play_back_started_) { |
| play_back_started_ = true; |
| unsigned int thread_id; |
| if (decode_thread_->Start(thread_id)) { |
| WEBRTC_TRACE(kTraceStateInfo, kTraceVideo, ViEId(engine_id_, id_), |
| "ViEFilePlayer::FrameCallbackChanged() Started file decode" |
| " thread %u", thread_id); |
| } else { |
| WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_, id_), |
| "ViEFilePlayer::FrameCallbackChanged() Failed to start " |
| "file decode thread."); |
| } |
| } else if (!file_player_->IsPlayingFile()) { |
| if (file_player_->StartPlayingVideoFile(file_name_, false, |
| !audio_stream_) != 0) { |
| WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_, id_), |
| "ViEFilePlayer::FrameCallbackChanged(), Failed to restart " |
| "the file player."); |
| } |
| } |
| } |
| video_clients_ = ViEFrameProviderBase::NumberOfRegisteredFrameCallbacks(); |
| return 0; |
| } |
| |
| bool ViEFilePlayer::FilePlayDecodeThreadFunction(void* obj) { |
| return static_cast<ViEFilePlayer*>(obj)->FilePlayDecodeProcess(); |
| } |
| |
| bool ViEFilePlayer::FilePlayDecodeProcess() { |
| if (decode_event_->Wait(kThreadWaitTimeMs) == kEventSignaled) { |
| if (audio_stream_ && audio_clients_ == 0) { |
| // There is audio but no one cares, read the audio here. |
| Read(NULL, 0); |
| } |
| if (file_player_->TimeUntilNextVideoFrame() < 10) { |
| // Less than 10ms to next videoframe. |
| if (file_player_->GetVideoFromFile(decoded_video_) != 0) { |
| } |
| } |
| if (decoded_video_.Length() > 0) { |
| if (local_audio_channel_ != -1 && voe_video_sync_) { |
| // We are playing audio locally. |
| int audio_delay = 0; |
| if (voe_video_sync_->GetPlayoutBufferSize(audio_delay) == 0) { |
| decoded_video_.SetRenderTime(decoded_video_.RenderTimeMs() + |
| audio_delay); |
| } |
| } |
| DeliverFrame(decoded_video_); |
| decoded_video_.SetLength(0); |
| } |
| } |
| return true; |
| } |
| |
| int ViEFilePlayer::StopPlay() { |
| // Only called from destructor. |
| if (decode_thread_) { |
| decode_thread_->SetNotAlive(); |
| if (decode_thread_->Stop()) { |
| delete decode_thread_; |
| } else { |
| assert(!"ViEFilePlayer::StopPlay() Failed to stop decode thread"); |
| WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_, id_), |
| "ViEFilePlayer::StartPlay() Failed to stop file decode " |
| "thread."); |
| } |
| } |
| decode_thread_ = NULL; |
| if (decode_event_) { |
| decode_event_->StopTimer(); |
| } |
| StopPlayAudio(); |
| |
| if (voe_file_interface_) { |
| voe_file_interface_->Release(); |
| voe_file_interface_ = NULL; |
| } |
| if (voe_video_sync_) { |
| voe_video_sync_->Release(); |
| voe_video_sync_ = NULL; |
| } |
| |
| if (file_player_) { |
| file_player_->StopPlayingFile(); |
| FilePlayer::DestroyFilePlayer(file_player_); |
| file_player_ = NULL; |
| } |
| return 0; |
| } |
| |
| int ViEFilePlayer::StopPlayAudio() { |
| // Stop sending audio. |
| |
| std::set<int>::iterator it = audio_channels_sending_.begin(); |
| while (it != audio_channels_sending_.end()) { |
| StopSendAudioOnChannel(*it); |
| // StopSendAudioOnChannel erases the item from the map. |
| it = audio_channels_sending_.begin(); |
| } |
| |
| // Stop local audio playback. |
| if (local_audio_channel_ != -1) { |
| StopPlayAudioLocally(local_audio_channel_); |
| } |
| local_audio_channel_ = -1; |
| audio_channel_buffers_.clear(); |
| audio_clients_ = 0; |
| return 0; |
| } |
| |
| int ViEFilePlayer::Read(void* buf, int len) { |
| // Protect from simultaneous reading from multiple channels. |
| CriticalSectionScoped lock(*audio_cs_); |
| if (NeedsAudioFromFile(buf)) { |
| // We will run the VoE in 16KHz. |
| if (file_player_->Get10msAudioFromFile(decoded_audio_, |
| decoded_audio_length_, 16000) != 0) { |
| // No data. |
| decoded_audio_length_ = 0; |
| return 0; |
| } |
| // 2 bytes per sample. |
| decoded_audio_length_ *= 2; |
| if (buf) { |
| audio_channel_buffers_.push_back(buf); |
| } |
| } else { |
| // No need for new audiobuffer from file, ie the buffer read from file has |
| // not been played on this channel. |
| } |
| if (buf) { |
| memcpy(buf, decoded_audio_, decoded_audio_length_); |
| } |
| return decoded_audio_length_; |
| } |
| |
| bool ViEFilePlayer::NeedsAudioFromFile(void* buf) { |
| bool needs_new_audio = false; |
| if (audio_channel_buffers_.size() == 0) { |
| return true; |
| } |
| |
| // Check if we the buf already have read the current audio. |
| for (std::list<void*>::iterator it = audio_channel_buffers_.begin(); |
| it != audio_channel_buffers_.end(); ++it) { |
| if (*it == buf) { |
| needs_new_audio = true; |
| audio_channel_buffers_.erase(it); |
| break; |
| } |
| } |
| return needs_new_audio; |
| } |
| |
| void ViEFilePlayer::PlayFileEnded(const WebRtc_Word32 id) { |
| WEBRTC_TRACE(kTraceInfo, kTraceVideo, ViEId(engine_id_, id), |
| "%s: file_id %d", __FUNCTION__, id_); |
| file_player_->StopPlayingFile(); |
| |
| CriticalSectionScoped lock(*feedback_cs_); |
| if (observer_) { |
| observer_->PlayFileEnded(id_); |
| } |
| } |
| |
| bool ViEFilePlayer::IsObserverRegistered() { |
| CriticalSectionScoped lock(*feedback_cs_); |
| return observer_ != NULL; |
| } |
| |
| int ViEFilePlayer::RegisterObserver(ViEFileObserver& observer) { |
| CriticalSectionScoped lock(*feedback_cs_); |
| if (observer_) { |
| return -1; |
| } |
| observer_ = &observer; |
| return 0; |
| } |
| |
| int ViEFilePlayer::DeRegisterObserver() { |
| CriticalSectionScoped lock(*feedback_cs_); |
| observer_ = NULL; |
| return 0; |
| } |
| |
| int ViEFilePlayer::SendAudioOnChannel(const int audio_channel, |
| bool mix_microphone, |
| float volume_scaling) { |
| if (!voe_file_interface_) { |
| WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_, id_), |
| "%s No VEFile interface.", __FUNCTION__); |
| return -1; |
| } |
| if (voe_file_interface_->StartPlayingFileAsMicrophone(audio_channel, this, |
| mix_microphone, |
| kFileFormatPcm16kHzFile, |
| volume_scaling) != 0) { |
| WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_, id_), |
| "ViEFilePlayer::SendAudioOnChannel() " |
| "VE_StartPlayingFileAsMicrophone failed. audio_channel %d, " |
| " mix_microphone %d, volume_scaling %.2f", |
| audio_channel, mix_microphone, volume_scaling); |
| return -1; |
| } |
| audio_channels_sending_.insert(audio_channel); |
| |
| CriticalSectionScoped lock(*audio_cs_); |
| audio_clients_++; |
| return 0; |
| } |
| |
| int ViEFilePlayer::StopSendAudioOnChannel(const int audio_channel) { |
| int result = 0; |
| std::set<int>::iterator it = audio_channels_sending_.find(audio_channel); |
| if (it == audio_channels_sending_.end()) { |
| WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_, id_), |
| "ViEFilePlayer::StopSendAudioOnChannel AudioChannel %d not " |
| "sending", audio_channel); |
| return -1; |
| } |
| result = voe_file_interface_->StopPlayingFileAsMicrophone(audio_channel); |
| if (result != 0) { |
| WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_, id_), |
| "ViEFilePlayer::StopSendAudioOnChannel() " |
| "VE_StopPlayingFileAsMicrophone failed. audio_channel %d", |
| audio_channel); |
| } |
| audio_channels_sending_.erase(audio_channel); |
| CriticalSectionScoped lock(*audio_cs_); |
| audio_clients_--; |
| assert(audio_clients_ >= 0); |
| return 0; |
| } |
| |
| int ViEFilePlayer::PlayAudioLocally(const int audio_channel, |
| float volume_scaling) { |
| if (!voe_file_interface_) { |
| WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_, id_), |
| "%s No VEFile interface.", __FUNCTION__); |
| return -1; |
| } |
| if (voe_file_interface_->StartPlayingFileLocally(audio_channel, this, |
| kFileFormatPcm16kHzFile, |
| volume_scaling) != 0) { |
| WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_, id_), |
| "%s VE_StartPlayingFileAsMicrophone failed. audio_channel %d," |
| " mix_microphone %d, volume_scaling %.2f", |
| __FUNCTION__, audio_channel, volume_scaling); |
| return -1; |
| } |
| |
| CriticalSectionScoped lock(*audio_cs_); |
| local_audio_channel_ = audio_channel; |
| audio_clients_++; |
| return 0; |
| } |
| |
| int ViEFilePlayer::StopPlayAudioLocally(const int audio_channel) { |
| if (!voe_file_interface_) { |
| WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_, id_), |
| "%s No VEFile interface.", __FUNCTION__); |
| return -1; |
| } |
| if (voe_file_interface_->StopPlayingFileLocally(audio_channel) != 0) { |
| WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_, id_), |
| "%s VE_StopPlayingFileLocally failed. audio_channel %d.", |
| __FUNCTION__, audio_channel); |
| return -1; |
| } |
| |
| CriticalSectionScoped lock(*audio_cs_); |
| local_audio_channel_ = -1; |
| audio_clients_--; |
| return 0; |
| } |
| |
| int ViEFilePlayer::GetFileInformation(int engine_id, |
| const char* file_name, |
| VideoCodec& video_codec, |
| CodecInst& audio_codec, |
| const FileFormats file_format) { |
| WEBRTC_TRACE(kTraceInfo, kTraceVideo, engine_id, "%s ", __FUNCTION__); |
| |
| FilePlayer* file_player = FilePlayer::CreateFilePlayer(engine_id, |
| file_format); |
| if (!file_player) { |
| return -1; |
| } |
| |
| bool video_only = false; |
| |
| memset(&video_codec, 0, sizeof(video_codec)); |
| memset(&audio_codec, 0, sizeof(audio_codec)); |
| |
| if (file_player->StartPlayingVideoFile(file_name, false, false) != 0) { |
| video_only = true; |
| if (file_player->StartPlayingVideoFile(file_name, false, true) != 0) { |
| WEBRTC_TRACE(kTraceError, kTraceVideo, engine_id, |
| "%s Failed to open file.", __FUNCTION__); |
| FilePlayer::DestroyFilePlayer(file_player); |
| return -1; |
| } |
| } |
| |
| if (!video_only && file_player->AudioCodec(audio_codec) != 0) { |
| WEBRTC_TRACE(kTraceError, kTraceVideo, engine_id, |
| "%s Failed to get audio codec.", __FUNCTION__); |
| FilePlayer::DestroyFilePlayer(file_player); |
| return -1; |
| } |
| if (file_player->video_codec_info(video_codec) != 0) { |
| WEBRTC_TRACE(kTraceError, kTraceVideo, engine_id, |
| "%s Failed to get video codec.", __FUNCTION__); |
| FilePlayer::DestroyFilePlayer(file_player); |
| return -1; |
| } |
| FilePlayer::DestroyFilePlayer(file_player); |
| return 0; |
| } |
| |
| } // namespace webrtc |