Source.cxx 6.41 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright 2003-2019 The Music Player Daemon Project
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
 * http://www.musicpd.org
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include "Source.hxx"
#include "MusicChunk.hxx"
22 23
#include "filter/Filter.hxx"
#include "filter/Prepared.hxx"
24
#include "filter/plugins/ReplayGainFilterPlugin.hxx"
25
#include "pcm/Mix.hxx"
26
#include "thread/Mutex.hxx"
27 28 29 30 31
#include "util/ConstBuffer.hxx"
#include "util/RuntimeError.hxx"

#include <string.h>

32 33 34
AudioOutputSource::AudioOutputSource() noexcept {}
AudioOutputSource::~AudioOutputSource() noexcept = default;

35
AudioFormat
36
AudioOutputSource::Open(const AudioFormat audio_format, const MusicPipe &_pipe,
37 38
			PreparedFilter *prepared_replay_gain_filter,
			PreparedFilter *prepared_other_replay_gain_filter,
39
			PreparedFilter &prepared_filter)
40 41 42
{
	assert(audio_format.IsValid());

43 44
	if (!IsOpen() || &_pipe != &pipe.GetPipe()) {
		current_chunk = nullptr;
45
		pipe.Init(_pipe);
46
	}
47 48 49

	/* (re)open the filter */

50
	if (filter && audio_format != in_audio_format)
51 52 53 54
		/* the filter must be reopened on all input format
		   changes */
		CloseFilter();

55
	if (filter == nullptr)
56 57 58 59 60 61 62
		/* open the filter */
		OpenFilter(audio_format,
			   prepared_replay_gain_filter,
			   prepared_other_replay_gain_filter,
			   prepared_filter);

	in_audio_format = audio_format;
63
	return filter->GetOutAudioFormat();
64 65 66
}

void
67
AudioOutputSource::Close() noexcept
68 69 70 71
{
	assert(in_audio_format.IsValid());
	in_audio_format.Clear();

72
	Cancel();
73 74 75 76

	CloseFilter();
}

77
void
78
AudioOutputSource::Cancel() noexcept
79 80 81
{
	current_chunk = nullptr;
	pipe.Cancel();
82

83 84
	if (replay_gain_filter)
		replay_gain_filter->Reset();
85

86 87
	if (other_replay_gain_filter)
		other_replay_gain_filter->Reset();
88

89 90
	if (filter)
		filter->Reset();
91 92
}

93 94 95 96
void
AudioOutputSource::OpenFilter(AudioFormat audio_format,
			      PreparedFilter *prepared_replay_gain_filter,
			      PreparedFilter *prepared_other_replay_gain_filter,
97
			      PreparedFilter &prepared_filter)
98 99 100 101
try {
	assert(audio_format.IsValid());

	/* the replay_gain filter cannot fail here */
102 103 104 105 106 107
	if (prepared_other_replay_gain_filter) {
		other_replay_gain_serial = 0;
		other_replay_gain_filter =
			prepared_other_replay_gain_filter->Open(audio_format);
	}

108
	if (prepared_replay_gain_filter) {
109
		replay_gain_serial = 0;
110
		replay_gain_filter =
111 112
			prepared_replay_gain_filter->Open(audio_format);

113 114 115 116
		audio_format = replay_gain_filter->GetOutAudioFormat();

		assert(replay_gain_filter->GetOutAudioFormat() ==
		       other_replay_gain_filter->GetOutAudioFormat());
117
	}
118

119
	filter = prepared_filter.Open(audio_format);
120 121 122 123 124 125
} catch (...) {
	CloseFilter();
	throw;
}

void
126
AudioOutputSource::CloseFilter() noexcept
127
{
128 129 130
	replay_gain_filter.reset();
	other_replay_gain_filter.reset();
	filter.reset();
131 132 133 134
}

ConstBuffer<void>
AudioOutputSource::GetChunkData(const MusicChunk &chunk,
135
				Filter *current_replay_gain_filter,
136 137 138 139 140 141 142 143 144
				unsigned *replay_gain_serial_p)
{
	assert(!chunk.IsEmpty());
	assert(chunk.CheckFormat(in_audio_format));

	ConstBuffer<void> data(chunk.data, chunk.length);

	assert(data.size % in_audio_format.GetFrameSize() == 0);

145 146
	if (!data.empty() && current_replay_gain_filter != nullptr) {
		replay_gain_filter_set_mode(*current_replay_gain_filter,
147 148
					    replay_gain_mode);

149
		if (chunk.replay_gain_serial != *replay_gain_serial_p) {
150
			replay_gain_filter_set_info(*current_replay_gain_filter,
151 152 153 154 155 156
						    chunk.replay_gain_serial != 0
						    ? &chunk.replay_gain_info
						    : nullptr);
			*replay_gain_serial_p = chunk.replay_gain_serial;
		}

157
		data = current_replay_gain_filter->FilterPCM(data);
158 159 160 161 162 163 164 165
	}

	return data;
}

ConstBuffer<void>
AudioOutputSource::FilterChunk(const MusicChunk &chunk)
{
166
	auto data = GetChunkData(chunk, replay_gain_filter.get(),
167
				 &replay_gain_serial);
168
	if (data.empty())
169 170 171 172 173 174
		return data;

	/* cross-fade */

	if (chunk.other != nullptr) {
		auto other_data = GetChunkData(*chunk.other,
175
					       other_replay_gain_filter.get(),
176
					       &other_replay_gain_serial);
177
		if (other_data.empty())
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210
			return data;

		/* if the "other" chunk is longer, then that trailer
		   is used as-is, without mixing; it is part of the
		   "next" song being faded in, and if there's a rest,
		   it means cross-fading ends here */

		if (data.size > other_data.size)
			data.size = other_data.size;

		float mix_ratio = chunk.mix_ratio;
		if (mix_ratio >= 0)
			/* reverse the mix ratio (because the
			   arguments to pcm_mix() are reversed), but
			   only if the mix ratio is non-negative; a
			   negative mix ratio is a MixRamp special
			   case */
			mix_ratio = 1.0 - mix_ratio;

		void *dest = cross_fade_buffer.Get(other_data.size);
		memcpy(dest, other_data.data, other_data.size);
		if (!pcm_mix(cross_fade_dither, dest, data.data, data.size,
			     in_audio_format.format,
			     mix_ratio))
			throw FormatRuntimeError("Cannot cross-fade format %s",
						 sample_format_to_string(in_audio_format.format));

		data.data = dest;
		data.size = other_data.size;
	}

	/* apply filter chain */

211
	return filter->FilterPCM(data);
212
}
213 214

bool
215
AudioOutputSource::Fill(Mutex &mutex)
216 217
{
	if (current_chunk != nullptr && pending_tag == nullptr &&
218
	    pending_data.empty())
219 220 221 222 223 224 225 226 227
		pipe.Consume(*std::exchange(current_chunk, nullptr));

	if (current_chunk != nullptr)
		return true;

	current_chunk = pipe.Get();
	if (current_chunk == nullptr)
		return false;

228
	pending_tag = current_chunk->tag.get();
229 230

	try {
231 232 233 234
		/* release the mutex while the filter runs, because
		   that may take a while */
		const ScopeUnlock unlock(mutex);

235 236 237 238 239 240 241 242 243 244 245 246 247 248
		pending_data = pending_data.FromVoid(FilterChunk(*current_chunk));
	} catch (...) {
		current_chunk = nullptr;
		throw;
	}

	return true;
}

void
AudioOutputSource::ConsumeData(size_t nbytes) noexcept
{
	pending_data.skip_front(nbytes);

249
	if (pending_data.empty())
250 251
		pipe.Consume(*std::exchange(current_chunk, nullptr));
}
252 253 254 255 256 257 258 259

ConstBuffer<void>
AudioOutputSource::Flush()
{
	return filter
		? filter->Flush()
		: nullptr;
}