diff --git a/app/codec/encoder.cpp b/app/codec/encoder.cpp index ba109d678..5392fff9d 100644 --- a/app/codec/encoder.cpp +++ b/app/codec/encoder.cpp @@ -82,12 +82,6 @@ QString Encoder::FilenameRemoveDigitPlaceholder(QString filename) return filename.remove(kImageSequenceRemoveDigits); } -void Encoder::WriteAudio(AudioParams pcm_info, const QString &pcm_filename) -{ - QFile f(pcm_filename); - WriteAudio(pcm_info, &f); -} - EncodingParams::EncodingParams() : video_enabled_(false), video_bit_rate_(0), diff --git a/app/codec/encoder.h b/app/codec/encoder.h index f8693aa26..5bcb8738b 100644 --- a/app/codec/encoder.h +++ b/app/codec/encoder.h @@ -29,6 +29,7 @@ #include "codec/exportcodec.h" #include "codec/exportformat.h" #include "codec/frame.h" +#include "codec/samplebuffer.h" #include "common/timerange.h" #include "render/audioparams.h" #include "render/videoparams.h" @@ -172,10 +173,7 @@ public slots: virtual bool Open() = 0; virtual bool WriteFrame(olive::FramePtr frame, olive::rational time) = 0; - virtual void WriteAudio(olive::AudioParams pcm_info, - QIODevice *file) = 0; - void WriteAudio(olive::AudioParams pcm_info, - const QString& pcm_filename); + virtual bool WriteAudio(olive::SampleBufferPtr audio) = 0; virtual void Close() = 0; diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index 3bdfae0aa..34c21ea1b 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -434,7 +434,7 @@ FootageDescription FFmpegDecoder::Probe(const QString &filename, const QAtomicIn QString FFmpegDecoder::FFmpegError(int error_code) { char err[1024]; - av_strerror(error_code, err, 1024); + av_strerror(error_code, err, 512); return QStringLiteral("%1 %2").arg(QString::number(error_code), err); } @@ -487,8 +487,8 @@ bool FFmpegDecoder::ConformAudioInternal(const QString &filename, const AudioPar if (ret == AVERROR_EOF) { success = true; } else { - char err_str[50]; - av_strerror(ret, err_str, 50); + char err_str[512]; + av_strerror(ret, err_str, 512); qWarning() << "Failed to conform:" << ret << err_str; } break; @@ -507,8 +507,8 @@ bool FFmpegDecoder::ConformAudioInternal(const QString &filename, const AudioPar frame->nb_samples); if (nb_samples < 0) { - char err_str[50]; - av_strerror(nb_samples, err_str, 50); + char err_str[512]; + av_strerror(nb_samples, err_str, 512); qWarning() << "libswresample failed with error:" << nb_samples << err_str; break; } @@ -1020,8 +1020,8 @@ bool FFmpegDecoder::Instance::Open(const char *filename, int stream_index) // Open codec error_code = avcodec_open2(codec_ctx_, codec, &opts_); if (error_code < 0) { - char buf[50]; - av_strerror(error_code, buf, 50); + char buf[512]; + av_strerror(error_code, buf, 512); qCritical() << "Failed to open codec" << codec->id << error_code << buf; return false; } diff --git a/app/codec/ffmpeg/ffmpegencoder.cpp b/app/codec/ffmpeg/ffmpegencoder.cpp index 440c7d7c2..761614cff 100644 --- a/app/codec/ffmpeg/ffmpegencoder.cpp +++ b/app/codec/ffmpeg/ffmpegencoder.cpp @@ -40,6 +40,7 @@ FFmpegEncoder::FFmpegEncoder(const EncodingParams ¶ms) : audio_stream_(nullptr), audio_codec_ctx_(nullptr), audio_resample_ctx_(nullptr), + audio_frame_(nullptr), open_(false) { } @@ -132,10 +133,10 @@ bool FFmpegEncoder::Open() // This is the equivalent pixel format above as an AVPixelFormat that swscale can understand AVPixelFormat src_alpha_pix_fmt = FFmpegUtils::GetFFmpegPixelFormat(video_conversion_fmt_, - VideoParams::kRGBAChannelCount); + VideoParams::kRGBAChannelCount); AVPixelFormat src_noalpha_pix_fmt = FFmpegUtils::GetFFmpegPixelFormat(video_conversion_fmt_, - VideoParams::kRGBChannelCount); + VideoParams::kRGBChannelCount); if (src_alpha_pix_fmt == AV_PIX_FMT_NONE || src_noalpha_pix_fmt == AV_PIX_FMT_NONE) { SetError(tr("Failed to find suitable pixel format for this buffer")); @@ -171,9 +172,10 @@ bool FFmpegEncoder::Open() } // Initialize an audio stream if it's enabled - if (params().audio_enabled() - && !InitializeStream(AVMEDIA_TYPE_AUDIO, &audio_stream_, &audio_codec_ctx_, params().audio_codec())) { - return false; + if (params().audio_enabled()) { + if (!InitializeStream(AVMEDIA_TYPE_AUDIO, &audio_stream_, &audio_codec_ctx_, params().audio_codec())) { + return false; + } } av_dump_format(fmt_ctx_, 0, filename_c_str, 1); @@ -261,107 +263,147 @@ fail: return success; } +bool FFmpegEncoder::WriteAudio(SampleBufferPtr audio) +{ + if (!InitializeResampleContext(audio)) { + qCritical() << "Failed to initialize resample context"; + return false; + } + + bool result = true; + + // Create input buffer + int input_sample_count = 0; + uint8_t** input_data = nullptr; + if (audio) { + input_sample_count = audio->sample_count(); + int input_linesize; + + av_samples_alloc_array_and_samples(&input_data, &input_linesize, audio->audio_params().channel_count(), + input_sample_count, FFmpegUtils::GetFFmpegSampleFormat(audio->audio_params().format(), true), 0); + + for (int i=0; iaudio_params().channel_count(); i++) { + memcpy(input_data[i], audio->data(i), input_sample_count * audio->audio_params().bytes_per_sample_per_channel()); + } + } + + // Create output buffer + int output_sample_count = input_sample_count ? swr_get_out_samples(audio_resample_ctx_, input_sample_count) : 102400; + uint8_t** output_data = nullptr; + int output_linesize; + av_samples_alloc_array_and_samples(&output_data, &output_linesize, audio_stream_->codecpar->channels, + output_sample_count, static_cast(audio_stream_->codecpar->format), 0); + + // Perform conversion + int converted = swr_convert(audio_resample_ctx_, output_data, output_sample_count, const_cast(input_data), input_sample_count); + if (converted > 0) { + // Split sample buffer into frames + for (int i=0; inb_samples) { + int copy_offset = audio_frame_offset_; + int frame_remaining_samples = audio_frame_->nb_samples - copy_offset; + int converted_remaining_samples = output_sample_count - i; + + int copy_length = qMin(frame_remaining_samples, converted_remaining_samples); + + av_samples_copy(audio_frame_->data, output_data, copy_offset, i, + copy_length, + audio_frame_->channels, static_cast(audio_frame_->format)); + + if (copy_length != frame_remaining_samples && input_data) { + // Frame didn't get all the samples it needed, save them for later + audio_frame_offset_ += copy_length; + } else { + // Got all the samples we needed, write the frame + audio_frame_->pts = audio_write_count_; + + if (!input_data) { + // Assume flushing and make this frame's samples = the amount copied + audio_frame_->nb_samples = copy_offset + copy_length; + qDebug() << "Writing" << audio_frame_->nb_samples << "flushed samples"; + } + + WriteAVFrame(audio_frame_, audio_codec_ctx_, audio_stream_); + audio_write_count_ += audio_frame_->nb_samples; + audio_frame_offset_ = 0; + } + } + } else if (converted < 0) { + FFmpegError(tr("Failed to resample audio"), converted); + result = false; + } + + // Free buffers created + if (output_data) { + av_freep(&output_data[0]); + av_freep(&output_data); + } + + if (input_data) { + av_freep(&input_data[0]); + av_freep(&input_data); + } + + return result; +} +/* void FFmpegEncoder::WriteAudio(AudioParams pcm_info, QIODevice* file) { - if (file->open(QFile::ReadOnly)) { - // Divide PCM stream into AVFrames - // See if the codec defines a number of samples per frame - int maximum_frame_samples = audio_codec_ctx_->frame_size; - if (!maximum_frame_samples) { - // If not, use another frame size - if (params().video_enabled()) { - // If we're encoding video, use enough samples to cover roughly one frame of video - maximum_frame_samples = params().audio_params().time_to_samples(params().video_params().frame_rate_as_time_base()); - } else { - // If no video, just use an arbitrary number - maximum_frame_samples = 256; - } + + // Keep track of sample count to use as each frame's timebase + int sample_counter = 0; + + while (true) { + // Calculate how many samples should input this frame + int64_t samples_needed = av_rescale_rnd(maximum_frame_samples + swr_get_delay(swr_ctx, pcm_info.sample_rate()), + audio_codec_ctx_->sample_rate, + pcm_info.sample_rate(), + AV_ROUND_UP); + + // Calculate how many bytes this is + int max_read = pcm_info.samples_to_bytes(samples_needed); + + // Read bytes from PCM + QByteArray input_data = file->read(max_read); + + // Use swresample to convert the data into the correct format + const char* input_data_array = input_data.constData(); + int converted = swr_convert(swr_ctx, + + // output data + frame->data, + + // output sample count (maximum amount of samples in output) + maximum_frame_samples, + + // input data + reinterpret_cast(&input_data_array), + + // input sample count (maximum amount of samples we read from pcm file) + pcm_info.bytes_to_samples(input_data.size())); + + // Update the frame's number of samples to the amount we actually received + frame->nb_samples = converted; + + // Update frame timestamp + frame->pts = sample_counter; + + // Increment timestamp for the next frame by the amount of samples in this one + sample_counter += converted; + + // Write the frame + if (!WriteAVFrame(frame, audio_codec_ctx_, audio_stream_)) { + qCritical() << "Failed to write audio AVFrame"; + break; } - SwrContext* swr_ctx = swr_alloc_set_opts(nullptr, - static_cast(audio_codec_ctx_->channel_layout), - audio_codec_ctx_->sample_fmt, - audio_codec_ctx_->sample_rate, - static_cast(pcm_info.channel_layout()), - FFmpegUtils::GetFFmpegSampleFormat(pcm_info.format()), - pcm_info.sample_rate(), - 0, - nullptr); - - swr_init(swr_ctx); - - // Loop through PCM queueing write events - AVFrame* frame = av_frame_alloc(); - - // Set up frame and allocate its buffers - frame->channel_layout = audio_codec_ctx_->channel_layout; - frame->nb_samples = maximum_frame_samples; - frame->format = audio_codec_ctx_->sample_fmt; - av_frame_get_buffer(frame, 0); - - // Keep track of sample count to use as each frame's timebase - int sample_counter = 0; - - while (true) { - // Calculate how many samples should input this frame - int64_t samples_needed = av_rescale_rnd(maximum_frame_samples + swr_get_delay(swr_ctx, pcm_info.sample_rate()), - audio_codec_ctx_->sample_rate, - pcm_info.sample_rate(), - AV_ROUND_UP); - - // Calculate how many bytes this is - int max_read = pcm_info.samples_to_bytes(samples_needed); - - // Read bytes from PCM - QByteArray input_data = file->read(max_read); - - // Use swresample to convert the data into the correct format - const char* input_data_array = input_data.constData(); - int converted = swr_convert(swr_ctx, - - // output data - frame->data, - - // output sample count (maximum amount of samples in output) - maximum_frame_samples, - - // input data - reinterpret_cast(&input_data_array), - - // input sample count (maximum amount of samples we read from pcm file) - pcm_info.bytes_to_samples(input_data.size())); - - // Update the frame's number of samples to the amount we actually received - frame->nb_samples = converted; - - // Update frame timestamp - frame->pts = sample_counter; - - // Increment timestamp for the next frame by the amount of samples in this one - sample_counter += converted; - - // Write the frame - if (!WriteAVFrame(frame, audio_codec_ctx_, audio_stream_)) { - qCritical() << "Failed to write audio AVFrame"; - break; - } - - // Break if we've reached the end point - if (file->atEnd()) { - break; - } + // Break if we've reached the end point + if (file->atEnd()) { + break; } - - av_frame_free(&frame); - - swr_free(&swr_ctx); - - file->close(); - } else { - qWarning() << "Failed to open audio IO device for encoding"; } } +*/ void FFmpegEncoder::Close() { @@ -376,6 +418,16 @@ void FFmpegEncoder::Close() open_ = false; } + if (audio_resample_ctx_) { + swr_init(audio_resample_ctx_); + audio_resample_ctx_ = nullptr; + } + + if (audio_frame_) { + av_frame_free(&audio_frame_); + audio_frame_ = nullptr; + } + if (video_alpha_scale_ctx_) { sws_freeContext(video_alpha_scale_ctx_); video_alpha_scale_ctx_ = nullptr; @@ -400,15 +452,19 @@ void FFmpegEncoder::Close() // NOTE: This also frees video_stream_ and audio_stream_ avformat_free_context(fmt_ctx_); fmt_ctx_ = nullptr; + video_stream_ = nullptr; + audio_stream_ = nullptr; } } void FFmpegEncoder::FFmpegError(const QString& context, int error_code) { - char err[128]; - av_strerror(error_code, err, 128); + char err[1024]; + av_strerror(error_code, err, 1024); - SetError(tr("%1: %2 %3").arg(context, err, QString::number(error_code))); + QString formatted_err = tr("%1: %2 %3").arg(context, formatted_err, QString::number(error_code)); + qDebug() << formatted_err; + SetError(formatted_err); } bool FFmpegEncoder::WriteAVFrame(AVFrame *frame, AVCodecContext* codec_ctx, AVStream* stream) @@ -569,9 +625,7 @@ bool FFmpegEncoder::InitializeStream(AVMediaType type, AVStream** stream_ptr, AV // Set custom options { - QHash::const_iterator i; - - for (i=params().video_opts().begin();i!=params().video_opts().end();i++) { + for (auto i=params().video_opts().begin();i!=params().video_opts().end();i++) { av_opt_set(codec_ctx->priv_data, i.key().toUtf8(), i.value().toUtf8(), AV_OPT_SEARCH_CHILDREN); } @@ -674,6 +728,8 @@ void FFmpegEncoder::FlushEncoders() } if (audio_codec_ctx_) { + WriteAudio(nullptr); + FlushCodecCtx(audio_codec_ctx_, audio_stream_); } } @@ -700,4 +756,63 @@ void FFmpegEncoder::FlushCodecCtx(AVCodecContext *codec_ctx, AVStream* stream) av_packet_free(&pkt); } +bool FFmpegEncoder::InitializeResampleContext(SampleBufferPtr audio) +{ + if (audio_resample_ctx_) { + return true; + } + + // Create resample context + audio_resample_ctx_ = swr_alloc_set_opts(nullptr, + static_cast(audio_codec_ctx_->channel_layout), + audio_codec_ctx_->sample_fmt, + audio_codec_ctx_->sample_rate, + static_cast(audio->audio_params().channel_layout()), + FFmpegUtils::GetFFmpegSampleFormat(audio->audio_params().format(), true), + audio->audio_params().sample_rate(), + 0, + nullptr); + if (!audio_resample_ctx_) { + return false; + } + + int err = swr_init(audio_resample_ctx_); + if (err < 0) { + FFmpegError(tr("Failed to create resampling context"), err); + return false; + } + + int max_frame_samples = audio_codec_ctx_->frame_size; + if (!max_frame_samples) { + // If not, use another frame size + if (params().video_enabled()) { + // If we're encoding video, use enough samples to cover roughly one frame of video + max_frame_samples = params().audio_params().time_to_samples(params().video_params().frame_rate_as_time_base()); + } else { + // If no video, just use an arbitrary number + max_frame_samples = 256; + } + } + + audio_frame_ = av_frame_alloc(); + if (!audio_frame_) { + return false; + } + + audio_frame_->channel_layout = audio_codec_ctx_->channel_layout; + audio_frame_->format = audio_codec_ctx_->sample_fmt; + audio_frame_->nb_samples = max_frame_samples; + + err = av_frame_get_buffer(audio_frame_, 0); + if (err < 0) { + FFmpegError(tr("Failed to create audio frame"), err); + return false; + } + + audio_frame_offset_ = 0; + audio_write_count_ = 0; + + return true; +} + } diff --git a/app/codec/ffmpeg/ffmpegencoder.h b/app/codec/ffmpeg/ffmpegencoder.h index 9d87e1a24..24d50e7fc 100644 --- a/app/codec/ffmpeg/ffmpegencoder.h +++ b/app/codec/ffmpeg/ffmpegencoder.h @@ -44,8 +44,7 @@ public: virtual bool WriteFrame(olive::FramePtr frame, olive::rational time) override; - virtual void WriteAudio(olive::AudioParams pcm_info, - QIODevice *file) override; + virtual bool WriteAudio(olive::SampleBufferPtr audio) override; virtual void Close() override; @@ -74,6 +73,8 @@ private: void FlushEncoders(); void FlushCodecCtx(AVCodecContext* codec_ctx, AVStream *stream); + bool InitializeResampleContext(SampleBufferPtr audio); + AVFormatContext* fmt_ctx_; AVStream* video_stream_; @@ -85,6 +86,9 @@ private: AVStream* audio_stream_; AVCodecContext* audio_codec_ctx_; SwrContext* audio_resample_ctx_; + AVFrame* audio_frame_; + int audio_frame_offset_; + int audio_write_count_; bool open_; diff --git a/app/codec/oiio/oiioencoder.cpp b/app/codec/oiio/oiioencoder.cpp index 917b6df23..ff9d9191f 100644 --- a/app/codec/oiio/oiioencoder.cpp +++ b/app/codec/oiio/oiioencoder.cpp @@ -62,9 +62,10 @@ bool OIIOEncoder::WriteFrame(FramePtr frame, rational time) return true; } -void OIIOEncoder::WriteAudio(AudioParams pcm_info, QIODevice *file) +bool OIIOEncoder::WriteAudio(SampleBufferPtr audio) { // Do nothing + return false; } void OIIOEncoder::Close() diff --git a/app/codec/oiio/oiioencoder.h b/app/codec/oiio/oiioencoder.h index d15a9ec95..555efe067 100644 --- a/app/codec/oiio/oiioencoder.h +++ b/app/codec/oiio/oiioencoder.h @@ -35,8 +35,7 @@ public slots: virtual bool Open() override; virtual bool WriteFrame(olive::FramePtr frame, olive::rational time) override; - virtual void WriteAudio(olive::AudioParams pcm_info, - QIODevice *file) override; + virtual bool WriteAudio(SampleBufferPtr audio) override; virtual void Close() override; diff --git a/app/common/ffmpegutils.cpp b/app/common/ffmpegutils.cpp index 9228e177e..6bc8b037f 100644 --- a/app/common/ffmpegutils.cpp +++ b/app/common/ffmpegutils.cpp @@ -67,21 +67,21 @@ AudioParams::Format FFmpegUtils::GetNativeSampleFormat(const AVSampleFormat &smp return AudioParams::kFormatInvalid; } -AVSampleFormat FFmpegUtils::GetFFmpegSampleFormat(const AudioParams::Format &smp_fmt) +AVSampleFormat FFmpegUtils::GetFFmpegSampleFormat(const AudioParams::Format &smp_fmt, bool planar) { switch (smp_fmt) { case AudioParams::kFormatUnsigned8: - return AV_SAMPLE_FMT_U8; + return planar ? AV_SAMPLE_FMT_U8P : AV_SAMPLE_FMT_U8; case AudioParams::kFormatSigned16: - return AV_SAMPLE_FMT_S16; + return planar ? AV_SAMPLE_FMT_S16P : AV_SAMPLE_FMT_S16; case AudioParams::kFormatSigned32: - return AV_SAMPLE_FMT_S32; + return planar ? AV_SAMPLE_FMT_S32P : AV_SAMPLE_FMT_S32; case AudioParams::kFormatSigned64: - return AV_SAMPLE_FMT_S64; + return planar ? AV_SAMPLE_FMT_S64P : AV_SAMPLE_FMT_S64; case AudioParams::kFormatFloat32: - return AV_SAMPLE_FMT_FLT; + return planar ? AV_SAMPLE_FMT_FLTP : AV_SAMPLE_FMT_FLT; case AudioParams::kFormatFloat64: - return AV_SAMPLE_FMT_DBL; + return planar ? AV_SAMPLE_FMT_DBLP : AV_SAMPLE_FMT_DBL; case AudioParams::kFormatInvalid: case AudioParams::kFormatCount: break; diff --git a/app/common/ffmpegutils.h b/app/common/ffmpegutils.h index d8121bdce..3e5ff105d 100644 --- a/app/common/ffmpegutils.h +++ b/app/common/ffmpegutils.h @@ -55,7 +55,7 @@ public: /** * @brief Returns an FFmpeg sample format type for a given native type */ - static AVSampleFormat GetFFmpegSampleFormat(const AudioParams::Format &smp_fmt); + static AVSampleFormat GetFFmpegSampleFormat(const AudioParams::Format &smp_fmt, bool planar = false); }; } diff --git a/app/task/export/export.cpp b/app/task/export/export.cpp index 9f694723f..6be0ae097 100644 --- a/app/task/export/export.cpp +++ b/app/task/export/export.cpp @@ -99,10 +99,6 @@ bool ExportTask::Run() params_.color_transform()); } - if (params_.audio_enabled()) { - audio_data_.SetParameters(audio_params()); - } - // Start render process TimeRangeList video_range, audio_range; @@ -112,7 +108,6 @@ bool ExportTask::Run() if (params_.audio_enabled()) { audio_range = {range}; - audio_data_.SetLength(range.length()); } Render(color_manager_, video_range, audio_range, RenderMode::kOnline, nullptr, @@ -121,13 +116,13 @@ bool ExportTask::Run() bool success = true; - if (params_.audio_enabled()) { - // Write audio data now - encoder_->WriteAudio(audio_params(), audio_data_.CreatePlaybackDevice(encoder_)); - } - encoder_->Close(); + if (!encoder_->GetError().isEmpty()) { + SetError(encoder_->GetError()); + success = false; + } + delete encoder_; // If cancelled, delete the file we made, which is always a file we created since we write to a @@ -187,7 +182,33 @@ void ExportTask::AudioDownloaded(const TimeRange &range, SampleBufferPtr samples adjusted_range -= params_.custom_range().in(); } - audio_data_.WritePCM(adjusted_range, samples, QDateTime::currentMSecsSinceEpoch()); + if (adjusted_range.in() == audio_time_) { + WriteAudioLoop(adjusted_range, samples); + } else { + audio_map_.insert(adjusted_range, samples); + } +} + +void ExportTask::WriteAudioLoop(const TimeRange& time, SampleBufferPtr samples) +{ + encoder_->WriteAudio(samples); + audio_time_ = time.out(); + + for (auto it=audio_map_.begin(); it!=audio_map_.end(); it++) { + TimeRange t = it.key(); + SampleBufferPtr s = it.value(); + + if (t.in() == audio_time_) { + // Erase from audio map since we're just about to write it + audio_map_.erase(it); + + // Call recursively to write the next sample buffer + WriteAudioLoop(t, s); + + // Break out of loop + break; + } + } } } diff --git a/app/task/export/export.h b/app/task/export/export.h index a29f2ce4f..b02691b54 100644 --- a/app/task/export/export.h +++ b/app/task/export/export.h @@ -48,8 +48,12 @@ protected: } private: + void WriteAudioLoop(const TimeRange &time, SampleBufferPtr samples); + QHash time_map_; + QHash audio_map_; + ColorManager* color_manager_; ExportParams params_; @@ -60,7 +64,7 @@ private: int64_t frame_time_; - AudioPlaybackCache audio_data_; + rational audio_time_; }; diff --git a/app/task/render/render.cpp b/app/task/render/render.cpp index da5336024..badf1faed 100644 --- a/app/task/render/render.cpp +++ b/app/task/render/render.cpp @@ -57,17 +57,24 @@ bool RenderTask::Render(ColorManager* manager, qint64 job_time = QDateTime::currentMSecsSinceEpoch(); // Queue audio jobs - foreach (const TimeRange& r, audio_range) { + foreach (const TimeRange& range, audio_range) { // Don't count audio progress, since it's generally a lot faster than video and is weighted at // 50%, which makes the progress bar look weird to the uninitiated //total_length += r.length().toDouble(); - IncrementRunningTickets(); + rational r = range.in(); + while (r != range.out()) { + rational end = qMin(range.out(), r+1); + TimeRange this_range(r, end); - RenderTicketWatcher* watcher = new RenderTicketWatcher(); - watcher->setProperty("range", QVariant::fromValue(r)); - PrepareWatcher(watcher, &watcher_thread); - watcher->SetTicket(RenderManager::instance()->RenderAudio(viewer_, r, audio_params_, false)); + RenderTicketWatcher* watcher = new RenderTicketWatcher(); + watcher->setProperty("range", QVariant::fromValue(this_range)); + PrepareWatcher(watcher, &watcher_thread); + IncrementRunningTickets(); + watcher->SetTicket(RenderManager::instance()->RenderAudio(viewer_, this_range, audio_params_, false)); + + r = end; + } } // Look up hashes