// The MediaStreamGraph guarantees that this is actually one block, for // AudioNodeStreams. void AudioNodeStream::ProcessInput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags) { EnsureTrack(AUDIO_TRACK, mSampleRate); // No more tracks will be coming mBuffer.AdvanceKnownTracksTime(STREAM_TIME_MAX); uint16_t outputCount = std::max(uint16_t(1), mEngine->OutputCount()); mLastChunks.SetLength(outputCount); // Consider this stream blocked if it has already finished output. Normally // mBlocked would reflect this, but due to rounding errors our audio track may // appear to extend slightly beyond aFrom, so we might not be blocked yet. bool blocked = mFinished || mBlocked.GetAt(aFrom); // If the stream has finished at this time, it will be blocked. if (mMuted || blocked) { for (uint16_t i = 0; i < outputCount; ++i) { mLastChunks[i].SetNull(WEBAUDIO_BLOCK_SIZE); } } else { // We need to generate at least one input uint16_t maxInputs = std::max(uint16_t(1), mEngine->InputCount()); OutputChunks inputChunks; inputChunks.SetLength(maxInputs); for (uint16_t i = 0; i < maxInputs; ++i) { ObtainInputBlock(inputChunks[i], i); } bool finished = false; if (maxInputs <= 1 && mEngine->OutputCount() <= 1) { mEngine->ProcessBlock(this, inputChunks[0], &mLastChunks[0], &finished); } else { mEngine->ProcessBlocksOnPorts(this, inputChunks, mLastChunks, &finished); } for (uint16_t i = 0; i < outputCount; ++i) { NS_ASSERTION(mLastChunks[i].GetDuration() == WEBAUDIO_BLOCK_SIZE, "Invalid WebAudio chunk size"); } if (finished) { mMarkAsFinishedAfterThisBlock = true; } if (mDisabledTrackIDs.Contains(static_cast<TrackID>(AUDIO_TRACK))) { for (uint32_t i = 0; i < outputCount; ++i) { mLastChunks[i].SetNull(WEBAUDIO_BLOCK_SIZE); } } } if (!blocked) { // Don't output anything while blocked AdvanceOutputSegment(); if (mMarkAsFinishedAfterThisBlock && (aFlags & ALLOW_FINISH)) { // This stream was finished the last time that we looked at it, and all // of the depending streams have finished their output as well, so now // it's time to mark this stream as finished. FinishOutput(); } } }
// The MediaStreamGraph guarantees that this is actually one block, for // AudioNodeStreams. void AudioNodeStream::ProduceOutput(GraphTime aFrom, GraphTime aTo) { if (mMarkAsFinishedAfterThisBlock) { // This stream was finished the last time that we looked at it, and all // of the depending streams have finished their output as well, so now // it's time to mark this stream as finished. FinishOutput(); } StreamBuffer::Track* track = EnsureTrack(); AudioSegment* segment = track->Get<AudioSegment>(); mLastChunks.SetLength(1); mLastChunks[0].SetNull(0); if (mInCycle) { // XXX DelayNode not supported yet so just produce silence mLastChunks[0].SetNull(WEBAUDIO_BLOCK_SIZE); } else { // We need to generate at least one input uint16_t maxInputs = std::max(uint16_t(1), mEngine->InputCount()); OutputChunks inputChunks; inputChunks.SetLength(maxInputs); for (uint16_t i = 0; i < maxInputs; ++i) { ObtainInputBlock(inputChunks[i], i); } bool finished = false; if (maxInputs <= 1 && mEngine->OutputCount() <= 1) { mEngine->ProduceAudioBlock(this, inputChunks[0], &mLastChunks[0], &finished); } else { mEngine->ProduceAudioBlocksOnPorts(this, inputChunks, mLastChunks, &finished); } if (finished) { mMarkAsFinishedAfterThisBlock = true; } } if (mKind == MediaStreamGraph::EXTERNAL_STREAM) { segment->AppendAndConsumeChunk(&mLastChunks[0]); } else { segment->AppendNullData(mLastChunks[0].GetDuration()); } for (uint32_t j = 0; j < mListeners.Length(); ++j) { MediaStreamListener* l = mListeners[j]; AudioChunk copyChunk = mLastChunks[0]; AudioSegment tmpSegment; tmpSegment.AppendAndConsumeChunk(©Chunk); l->NotifyQueuedTrackChanges(Graph(), AUDIO_NODE_STREAM_TRACK_ID, IdealAudioRate(), segment->GetDuration(), 0, tmpSegment); } }
// The MediaStreamGraph guarantees that this is actually one block, for // AudioNodeStreams. void AudioNodeStream::ProduceOutput(GraphTime aFrom, GraphTime aTo) { if (mMarkAsFinishedAfterThisBlock) { // This stream was finished the last time that we looked at it, and all // of the depending streams have finished their output as well, so now // it's time to mark this stream as finished. FinishOutput(); } EnsureTrack(AUDIO_NODE_STREAM_TRACK_ID, mSampleRate); uint16_t outputCount = std::max(uint16_t(1), mEngine->OutputCount()); mLastChunks.SetLength(outputCount); if (mInCycle) { // XXX DelayNode not supported yet so just produce silence for (uint16_t i = 0; i < outputCount; ++i) { mLastChunks[i].SetNull(WEBAUDIO_BLOCK_SIZE); } } else { for (uint16_t i = 0; i < outputCount; ++i) { mLastChunks[i].SetNull(0); } // We need to generate at least one input uint16_t maxInputs = std::max(uint16_t(1), mEngine->InputCount()); OutputChunks inputChunks; inputChunks.SetLength(maxInputs); for (uint16_t i = 0; i < maxInputs; ++i) { ObtainInputBlock(inputChunks[i], i); } bool finished = false; if (maxInputs <= 1 && mEngine->OutputCount() <= 1) { mEngine->ProduceAudioBlock(this, inputChunks[0], &mLastChunks[0], &finished); } else { mEngine->ProduceAudioBlocksOnPorts(this, inputChunks, mLastChunks, &finished); } if (finished) { mMarkAsFinishedAfterThisBlock = true; } } if (mDisabledTrackIDs.Contains(AUDIO_NODE_STREAM_TRACK_ID)) { for (uint32_t i = 0; i < mLastChunks.Length(); ++i) { mLastChunks[i].SetNull(WEBAUDIO_BLOCK_SIZE); } } AdvanceOutputSegment(); }
void PageRecord::StopPage(bool save) { if(!m_page_started) return; StopOutput(true); StopInput(); Logger::LogInfo("[PageRecord::StopPage] " + tr("Stopping page ...")); if(m_output_manager != NULL) { // stop the output if(save) FinishOutput(); m_output_manager.reset(); // delete the file if it isn't needed if(!save && m_file_protocol.isNull()) { if(QFileInfo(m_output_settings.file).exists()) QFile(m_output_settings.file).remove(); } } // destroy the GLInject input m_gl_inject_input.reset(); // stop JACK input #if SSR_USE_JACK m_jack_input.reset(); #endif Logger::LogInfo("[PageRecord::StopPage] " + tr("Stopped page.")); m_page_started = false; UpdateSysTray(); OnUpdateSoundNotifications(); m_timer_update_info->stop(); OnUpdateInformation(); }
// The MediaStreamGraph guarantees that this is actually one block, for // AudioNodeStreams. void AudioNodeStream::ProduceOutput(GraphTime aFrom, GraphTime aTo) { StreamBuffer::Track* track = EnsureTrack(); AudioChunk outputChunk; AudioSegment* segment = track->Get<AudioSegment>(); outputChunk.SetNull(0); if (mInCycle) { // XXX DelayNode not supported yet so just produce silence outputChunk.SetNull(WEBAUDIO_BLOCK_SIZE); } else { AudioChunk tmpChunk; AudioChunk* inputChunk = ObtainInputBlock(&tmpChunk); bool finished = false; mEngine->ProduceAudioBlock(this, *inputChunk, &outputChunk, &finished); if (finished) { FinishOutput(); } } mLastChunk = outputChunk; if (mKind == MediaStreamGraph::EXTERNAL_STREAM) { segment->AppendAndConsumeChunk(&outputChunk); } else { segment->AppendNullData(outputChunk.GetDuration()); } for (uint32_t j = 0; j < mListeners.Length(); ++j) { MediaStreamListener* l = mListeners[j]; AudioChunk copyChunk = outputChunk; AudioSegment tmpSegment; tmpSegment.AppendAndConsumeChunk(©Chunk); l->NotifyQueuedTrackChanges(Graph(), AUDIO_NODE_STREAM_TRACK_ID, IdealAudioRate(), segment->GetDuration(), 0, tmpSegment); } }
// The MediaStreamGraph guarantees that this is actually one block, for // AudioNodeStreams. void AudioNodeStream::ProcessInput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags) { uint16_t outputCount = mLastChunks.Length(); MOZ_ASSERT(outputCount == std::max(uint16_t(1), mEngine->OutputCount())); if (!mIsActive) { // mLastChunks are already null. #ifdef DEBUG for (const auto& chunk : mLastChunks) { MOZ_ASSERT(chunk.IsNull()); } #endif } else if (InMutedCycle()) { mInputChunks.Clear(); for (uint16_t i = 0; i < outputCount; ++i) { mLastChunks[i].SetNull(WEBAUDIO_BLOCK_SIZE); } } else { // We need to generate at least one input uint16_t maxInputs = std::max(uint16_t(1), mEngine->InputCount()); mInputChunks.SetLength(maxInputs); for (uint16_t i = 0; i < maxInputs; ++i) { ObtainInputBlock(mInputChunks[i], i); } bool finished = false; if (mPassThrough) { MOZ_ASSERT(outputCount == 1, "For now, we only support nodes that have one output port"); mLastChunks[0] = mInputChunks[0]; } else { if (maxInputs <= 1 && outputCount <= 1) { mEngine->ProcessBlock(this, aFrom, mInputChunks[0], &mLastChunks[0], &finished); } else { mEngine->ProcessBlocksOnPorts(this, mInputChunks, mLastChunks, &finished); } } for (uint16_t i = 0; i < outputCount; ++i) { NS_ASSERTION(mLastChunks[i].GetDuration() == WEBAUDIO_BLOCK_SIZE, "Invalid WebAudio chunk size"); } if (finished) { mMarkAsFinishedAfterThisBlock = true; if (mIsActive) { ScheduleCheckForInactive(); } } if (mDisabledTrackIDs.Contains(static_cast<TrackID>(AUDIO_TRACK))) { for (uint32_t i = 0; i < outputCount; ++i) { mLastChunks[i].SetNull(WEBAUDIO_BLOCK_SIZE); } } } if (!mFinished) { // Don't output anything while finished if (mFlags & EXTERNAL_OUTPUT) { AdvanceOutputSegment(); } if (mMarkAsFinishedAfterThisBlock && (aFlags & ALLOW_FINISH)) { // This stream was finished the last time that we looked at it, and all // of the depending streams have finished their output as well, so now // it's time to mark this stream as finished. if (mFlags & EXTERNAL_OUTPUT) { FinishOutput(); } FinishOnGraphThread(); } } }
// The MediaStreamGraph guarantees that this is actually one block, for // AudioNodeStreams. void AudioNodeStream::ProcessInput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags) { if (!mFinished) { EnsureTrack(AUDIO_TRACK); } // No more tracks will be coming mBuffer.AdvanceKnownTracksTime(STREAM_TIME_MAX); uint16_t outputCount = mLastChunks.Length(); MOZ_ASSERT(outputCount == std::max(uint16_t(1), mEngine->OutputCount())); // Consider this stream blocked if it has already finished output. Normally // mBlocked would reflect this, but due to rounding errors our audio track may // appear to extend slightly beyond aFrom, so we might not be blocked yet. bool blocked = mFinished || mBlocked.GetAt(aFrom); // If the stream has finished at this time, it will be blocked. if (blocked || InMutedCycle()) { mInputChunks.Clear(); for (uint16_t i = 0; i < outputCount; ++i) { mLastChunks[i].SetNull(WEBAUDIO_BLOCK_SIZE); } } else { // We need to generate at least one input uint16_t maxInputs = std::max(uint16_t(1), mEngine->InputCount()); mInputChunks.SetLength(maxInputs); for (uint16_t i = 0; i < maxInputs; ++i) { ObtainInputBlock(mInputChunks[i], i); } bool finished = false; if (mPassThrough) { MOZ_ASSERT(outputCount == 1, "For now, we only support nodes that have one output port"); mLastChunks[0] = mInputChunks[0]; } else { if (maxInputs <= 1 && outputCount <= 1) { mEngine->ProcessBlock(this, mInputChunks[0], &mLastChunks[0], &finished); } else { mEngine->ProcessBlocksOnPorts(this, mInputChunks, mLastChunks, &finished); } } for (auto& chunk : mInputChunks) { // If the buffer is shared then it won't be reused, so release the // reference now. Keep the channel data array to save a free/alloc // pair. chunk.ReleaseBufferIfShared(); } for (uint16_t i = 0; i < outputCount; ++i) { NS_ASSERTION(mLastChunks[i].GetDuration() == WEBAUDIO_BLOCK_SIZE, "Invalid WebAudio chunk size"); } if (finished) { mMarkAsFinishedAfterThisBlock = true; } if (mDisabledTrackIDs.Contains(static_cast<TrackID>(AUDIO_TRACK))) { for (uint32_t i = 0; i < outputCount; ++i) { mLastChunks[i].SetNull(WEBAUDIO_BLOCK_SIZE); } } } if (!blocked) { // Don't output anything while blocked AdvanceOutputSegment(); if (mMarkAsFinishedAfterThisBlock && (aFlags & ALLOW_FINISH)) { // This stream was finished the last time that we looked at it, and all // of the depending streams have finished their output as well, so now // it's time to mark this stream as finished. FinishOutput(); } } }
void PageRecord::StartPage() { if(m_page_started) return; assert(!m_input_started); assert(!m_output_started); // save the settings in case libav/ffmpeg decides to kill the process m_main_window->SaveSettings(); // clear the log m_textedit_log->clear(); // clear the preview if(m_previewing) { m_video_previewer->Reset(); m_audio_previewer->Reset(); } PageInput *page_input = m_main_window->GetPageInput(); PageOutput *page_output = m_main_window->GetPageOutput(); // get the video input settings m_video_area = page_input->GetVideoArea(); m_video_area_follow_fullscreen = page_input->GetVideoAreaFollowFullscreen(); m_video_x = page_input->GetVideoX(); m_video_y = page_input->GetVideoY(); #if SSR_USE_OPENGL_RECORDING if(m_video_area == PageInput::VIDEO_AREA_GLINJECT) { m_video_in_width = 0; m_video_in_height = 0; } else { #else { #endif m_video_in_width = page_input->GetVideoW(); m_video_in_height = page_input->GetVideoH(); } m_video_in_width = page_input->GetVideoW(); m_video_in_height = page_input->GetVideoH(); m_video_frame_rate = page_input->GetVideoFrameRate(); m_video_scaling = page_input->GetVideoScalingEnabled(); m_video_scaled_width = page_input->GetVideoScaledW(); m_video_scaled_height = page_input->GetVideoScaledH(); m_video_record_cursor = page_input->GetVideoRecordCursor(); // get the audio input settings m_audio_enabled = page_input->GetAudioEnabled(); m_audio_channels = 2; m_audio_sample_rate = 48000; m_audio_backend = page_input->GetAudioBackend(); #if SSR_USE_ALSA m_alsa_source = page_input->GetALSASourceName(); #endif #if SSR_USE_PULSEAUDIO m_pulseaudio_source = page_input->GetPulseAudioSourceName(); #endif #if SSR_USE_JACK bool jack_connect_system_capture = page_input->GetJackConnectSystemCapture(); bool jack_connect_system_playback = page_input->GetJackConnectSystemPlayback(); #endif // override sample rate for problematic cases (these are hard-coded for now) if(page_output->GetContainer() == PageOutput::CONTAINER_OTHER && page_output->GetContainerAVName() == "flv") { m_audio_sample_rate = 44100; } #if SSR_USE_OPENGL_RECORDING // get the glinject settings QString glinject_channel = page_input->GetGLInjectChannel(); bool glinject_relax_permissions = page_input->GetGLInjectRelaxPermissions(); QString glinject_command = page_input->GetGLInjectCommand(); QString glinject_working_directory = page_input->GetGLInjectWorkingDirectory(); bool glinject_auto_launch = page_input->GetGLInjectAutoLaunch(); bool glinject_limit_fps = page_input->GetGLInjectLimitFPS(); #endif // get file settings m_file_base = page_output->GetFile(); m_file_protocol = page_output->GetFileProtocol(); m_separate_files = page_output->GetSeparateFiles(); m_add_timestamp = page_output->GetAddTimestamp(); // get the output settings m_output_settings.file = QString(); // will be set later m_output_settings.container_avname = page_output->GetContainerAVName(); m_output_settings.video_codec_avname = page_output->GetVideoCodecAVName(); m_output_settings.video_kbit_rate = page_output->GetVideoKBitRate(); m_output_settings.video_options.clear(); m_output_settings.video_width = 0; m_output_settings.video_height = 0; m_output_settings.video_frame_rate = m_video_frame_rate; m_output_settings.video_allow_frame_skipping = page_output->GetVideoAllowFrameSkipping(); m_output_settings.audio_codec_avname = (m_audio_enabled)? page_output->GetAudioCodecAVName() : QString(); m_output_settings.audio_kbit_rate = page_output->GetAudioKBitRate(); m_output_settings.audio_options.clear(); m_output_settings.audio_channels = m_audio_channels; m_output_settings.audio_sample_rate = m_audio_sample_rate; // some codec-specific things // you can get more information about all these options by running 'ffmpeg -h' or 'avconv -h' from a terminal switch(page_output->GetVideoCodec()) { case PageOutput::VIDEO_CODEC_H264: { // x264 has a 'constant quality' mode, where the bit rate is simply set to whatever is needed to keep a certain quality. The quality is set // with the 'crf' option. 'preset' changes the encoding speed (and hence the efficiency of the compression) but doesn't really influence the quality, // which is great because it means you don't have to experiment with different bit rates and different speeds to get good results. m_output_settings.video_options.push_back(std::make_pair(QString("crf"), QString::number(page_output->GetH264CRF()))); m_output_settings.video_options.push_back(std::make_pair(QString("preset"), EnumToString(page_output->GetH264Preset()))); break; } case PageOutput::VIDEO_CODEC_VP8: { // The names of there parameters are very unintuitive. The two options we care about (because they change the speed) are 'deadline' and 'cpu-used'. // 'deadline=best' is unusably slow. 'deadline=good' is the normal setting, it tells the encoder to use the speed set with 'cpu-used'. Higher // numbers will use *less* CPU, confusingly, so a higher number is faster. I haven't done much testing with 'realtime' so I'm not sure if it's a good idea here. // It sounds useful, but I think it will use so much CPU that it will slow down the program that is being recorded. m_output_settings.video_options.push_back(std::make_pair(QString("deadline"), QString("good"))); m_output_settings.video_options.push_back(std::make_pair(QString("cpu-used"), QString::number(page_output->GetVP8CPUUsed()))); break; } case PageOutput::VIDEO_CODEC_OTHER: { m_output_settings.video_options = GetOptionsFromString(page_output->GetVideoOptions()); break; } default: break; // to keep GCC happy } switch(page_output->GetAudioCodec()) { case PageOutput::AUDIO_CODEC_OTHER: { m_output_settings.audio_options = GetOptionsFromString(page_output->GetAudioOptions()); break; } default: break; // to keep GCC happy } // hide the audio previewer if there is no audio GroupVisible({m_label_mic_icon, m_audio_previewer}, m_audio_enabled); Logger::LogInfo("[PageRecord::StartPage] " + tr("Starting page ...")); try { #if SSR_USE_OPENGL_RECORDING // for OpenGL recording, create the input now if(m_video_area == PageInput::VIDEO_AREA_GLINJECT) { if(glinject_auto_launch) GLInjectInput::LaunchApplication(glinject_channel, glinject_relax_permissions, glinject_command, glinject_working_directory); m_gl_inject_input.reset(new GLInjectInput(glinject_channel, glinject_relax_permissions, m_video_record_cursor, glinject_limit_fps, m_video_frame_rate)); } #endif #if SSR_USE_JACK if(m_audio_enabled) { // for JACK, start the input now if(m_audio_backend == PageInput::AUDIO_BACKEND_JACK) m_jack_input.reset(new JACKInput(jack_connect_system_capture, jack_connect_system_playback)); } #endif } catch(...) { Logger::LogError("[PageRecord::StartPage] " + tr("Error: Something went wrong during initialization.")); #if SSR_USE_OPENGL_RECORDING m_gl_inject_input.reset(); #endif #if SSR_USE_JACK m_jack_input.reset(); #endif } Logger::LogInfo("[PageRecord::StartPage] " + tr("Started page.")); m_page_started = true; m_recorded_something = false; m_wait_saving = false; m_error_occurred = false; UpdateSysTray(); #if SSR_USE_ALSA OnUpdateSoundNotifications(); #endif UpdateInput(); OnUpdateInformation(); m_timer_update_info->start(1000); } void PageRecord::StopPage(bool save) { if(!m_page_started) return; StopOutput(true); StopInput(); Logger::LogInfo("[PageRecord::StopPage] " + tr("Stopping page ...")); if(m_output_manager != NULL) { // stop the output if(save) FinishOutput(); m_output_manager.reset(); // delete the file if it isn't needed if(!save && m_file_protocol.isNull()) { if(QFileInfo(m_output_settings.file).exists()) QFile(m_output_settings.file).remove(); } } #if SSR_USE_OPENGL_RECORDING // stop GLInject input m_gl_inject_input.reset(); #endif #if SSR_USE_JACK // stop JACK input m_jack_input.reset(); #endif Logger::LogInfo("[PageRecord::StopPage] " + tr("Stopped page.")); m_page_started = false; UpdateSysTray(); #if SSR_USE_ALSA OnUpdateSoundNotifications(); #endif m_timer_update_info->stop(); OnUpdateInformation(); } void PageRecord::StartOutput() { assert(m_page_started); if(m_output_started) return; #if SSR_USE_ALSA if(m_simple_synth != NULL) { m_simple_synth->PlaySequence(SEQUENCE_RECORD_START.data(), SEQUENCE_RECORD_START.size()); usleep(200000); } #endif try { Logger::LogInfo("[PageRecord::StartOutput] " + tr("Starting output ...")); if(m_output_manager == NULL) { // set the file name m_output_settings.file = GetNewSegmentFile(m_file_base, m_add_timestamp); // for X11 recording, update the video size (if possible) if(m_x11_input != NULL) m_x11_input->GetCurrentSize(&m_video_in_width, &m_video_in_height); #if SSR_USE_OPENGL_RECORDING // for OpenGL recording, detect the video size if(m_video_area == PageInput::VIDEO_AREA_GLINJECT && !m_video_scaling) { if(m_gl_inject_input == NULL) { Logger::LogError("[PageRecord::StartOutput] " + tr("Error: Could not get the size of the OpenGL application because the GLInject input has not been created.")); throw GLInjectException(); } m_gl_inject_input->GetCurrentSize(&m_video_in_width, &m_video_in_height); if(m_video_in_width == 0 && m_video_in_height == 0) { Logger::LogError("[PageRecord::StartOutput] " + tr("Error: Could not get the size of the OpenGL application. Either the " "application wasn't started correctly, or the application hasn't created an OpenGL window yet. If " "you want to start recording before starting the application, you have to enable scaling and enter " "the video size manually.")); throw GLInjectException(); } } #endif // calculate the output width and height if(m_video_scaling) { // Only even width and height is allowed because some pixel formats (e.g. YUV420) require this. m_output_settings.video_width = m_video_scaled_width / 2 * 2; m_output_settings.video_height = m_video_scaled_height / 2 * 2; #if SSR_USE_OPENGL_RECORDING } else if(m_video_area == PageInput::VIDEO_AREA_GLINJECT) { // The input size is the size of the OpenGL application and can't be changed. The output size is set to the current size of the application. m_output_settings.video_width = m_video_in_width / 2 * 2; m_output_settings.video_height = m_video_in_height / 2 * 2; #endif } else { // If the user did not explicitly select scaling, then don't force scaling just because the recording area is one pixel too large. // One missing row/column of pixels is probably better than a blurry video (and scaling is SLOW). m_video_in_width = m_video_in_width / 2 * 2; m_video_in_height = m_video_in_height / 2 * 2; m_output_settings.video_width = m_video_in_width; m_output_settings.video_height = m_video_in_height; } // start the output m_output_manager.reset(new OutputManager(m_output_settings)); } else { // start a new segment m_output_manager->GetSynchronizer()->NewSegment(); } Logger::LogInfo("[PageRecord::StartOutput] " + tr("Started output.")); m_output_started = true; m_recorded_something = true; UpdateSysTray(); UpdateRecordPauseButton(); UpdateInput(); } catch(...) { Logger::LogError("[PageRecord::StartOutput] " + tr("Error: Something went wrong during initialization.")); } }