1
/******************************************************************************
2
* Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock *
3
* Copyright © 2006-2012 Quality & Usability Lab, *
4
* Telekom Innovation Laboratories, TU Berlin *
6
* This file is part of the SoundScape Renderer (SSR). *
8
* The SSR is free software: you can redistribute it and/or modify it under *
9
* the terms of the GNU General Public License as published by the Free *
10
* Software Foundation, either version 3 of the License, or (at your option) *
11
* any later version. *
13
* The SSR is distributed in the hope that it will be useful, but WITHOUT ANY *
14
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS *
15
* FOR A PARTICULAR PURPOSE. *
16
* See the GNU General Public License for more details. *
18
* You should have received a copy of the GNU General Public License along *
19
* with this program. If not, see <http://www.gnu.org/licenses/>. *
21
* The SSR is a tool for real-time spatial audio reproduction providing a *
22
* variety of rendering algorithms. *
24
* http://spatialaudio.net/ssr ssr@spatialaudio.net *
25
******************************************************************************/
28
/// Audio player using ecasound (implementation).
30
#include <jack/jack.h> // for jack_client_name_size()
33
#include "audioplayer.h"
35
#include "ssr_global.h"
36
#include "apf/stringtools.h"
37
#include "posixpathtools.h"
39
using maptools::get_item;
41
/// delete the file map.
42
AudioPlayer::~AudioPlayer()
44
if (!_file_map.empty())
46
maptools::purge(_file_map);
47
VERBOSE2("AudioPlayer dtor: file map deleted.");
51
VERBOSE2("AudioPlayer dtor.");
56
* If the file is already opened, an existing instance of ecasound is used.
57
* @param audio_file_name name of the audio file, what did you think?
58
* @param channel select a channel of a multichannel file (starting with 1)
59
* @param loop temporary solution for a loop mode
60
* @return Name of the JACK port.
61
* @warning If @a audio_file_name uses symbolic links or such things it can
62
* happen that one file is opened several times.
64
std::string AudioPlayer::get_port_name(const std::string& audio_file_name,
65
int channel, bool loop)
69
auto registered_file = get_item(_file_map, audio_file_name);
70
if (registered_file != nullptr)
72
VERBOSE2("AudioPlayer: Input file '" + audio_file_name
73
+ "' already registered.");
74
if (channel > registered_file->get_channels())
76
ERROR("AudioPlayer: Channel " << channel << " doesn't exist in '"
77
+ audio_file_name + "'!");
81
else // file not yet registered
83
auto temp = Soundfile::create(audio_file_name, loop);
86
WARNING("AudioPlayer: Initialization of soundfile '" + audio_file_name
90
if (channel > temp->get_channels())
92
ERROR("AudioPlayer: Channel " << channel << " doesn't exist in '"
93
+ audio_file_name + "'!");
94
// if wrong channel is requested, audiofile is not registered.
97
registered_file = temp.get();
98
_file_map[audio_file_name] = temp.release();
100
return registered_file->get_client_name() + ":"
101
+ registered_file->output_prefix + "_" + apf::str::A2S(channel);
105
* @param audio_file_name the audio file you want to know the length of.
106
* @warning If the file wasn't loaded before, 0 is returned!
108
long int AudioPlayer::get_file_length(const std::string& audio_file_name) const
110
const Soundfile* const file = get_item(_file_map, audio_file_name);
111
return file ? file->get_length() : 0;
115
AudioPlayer::Soundfile::get_format(const std::string& filename
116
, size_t& channels, size_t& sample_rate)
118
ECA_CONTROL_INTERFACE eca;
119
eca.command("cs-add dummy_chainsetup");
120
eca.command("c-add dummy_chain");
122
eca.command("ai-add sndfile,"
123
+ posixpathtools::get_escaped_filename(filename));
124
eca.command("ao-add null");
125
eca.command("cs-connect");
128
throw soundfile_error("get_format(): " + eca.last_error());
130
eca.command("ai-index-select 1");
131
eca.command("ai-get-format");
132
std::string str = eca.last_string();
133
eca.command("cs-disconnect");
134
eca.command("c-remove");
135
eca.command("cs-remove");
137
std::replace(str.begin(), str.end(), ',', ' ');
138
std::istringstream iss(str);
140
iss >> format >> channels >> sample_rate;
143
throw soundfile_error("Couldn't convert format string!");
145
assert(sample_rate >= 1);
151
AudioPlayer::Soundfile::_get_jack_sample_rate()
153
ECA_CONTROL_INTERFACE eca;
154
eca.command("cs-add dummy_chainsetup");
155
eca.command("c-add dummy_chain");
157
eca.command("ai-add jack");
158
eca.command("ao-add null");
159
eca.command("cs-connect");
162
throw soundfile_error("_get_jack_sample_rate(): " + eca.last_error());
164
eca.command("ai-get-format");
165
std::string str = eca.last_string();
166
eca.command("cs-disconnect");
167
eca.command("c-remove");
168
eca.command("cs-remove");
170
std::replace(str.begin(), str.end(), ',', ' ');
171
std::istringstream iss(str);
173
size_t channels, sample_rate;
174
iss >> format >> channels >> sample_rate;
177
throw soundfile_error("Couldn't convert string for getting sample rate!");
179
assert(sample_rate >= 1);
185
* @param filename name of soundfile
186
* @param loop enable loop mode
187
* @param prefix prefix used for channel names
188
* @throw soundfile_error
189
* @attention The soundfile must have the same sampling rate as the JACK server.
190
* @warning If the name of the soundfile (including path) is longer than the
191
* maximum allowed JACK client-name, it gets truncated. But if another file is
192
* truncated to the same client-name, ecasound probably wants to add "_2" to
193
* distinguish them but this will then be longer than the allowed length.
195
* UPDATE: This is already taken care of, but only up to "_99". So if you have
196
* more than 99 equal client-names, you will get a problem. Hopefully, you
197
* don't have that many ...
199
AudioPlayer::Soundfile::Soundfile(const std::string& filename, bool loop,
200
const std::string& prefix) throw (soundfile_error) :
201
output_prefix(prefix),
206
_sample_format = get_format(_filename, _channels, _sample_rate);
207
size_t jack_sample_rate = _get_jack_sample_rate();
209
_eca.command("cs-add real_chainsetup");
210
_eca.command("c-add real_chain");
211
_eca.command("cs-set-audio-format ,"
212
+ apf::str::A2S(_channels) + "," + apf::str::A2S(jack_sample_rate));
214
std::string ai_add = "ai-add ";
217
ai_add += "audioloop,";
220
if (_sample_rate != jack_sample_rate)
222
WARNING("'" + _filename
223
+ "' has a different sample rate than JACK! ("
224
+ apf::str::A2S(_sample_rate) + " vs. "
225
+ apf::str::A2S(jack_sample_rate) + ")");
226
ai_add += "resample-hq,auto,";
229
ai_add += "sndfile,";
230
ai_add += posixpathtools::get_escaped_filename(filename);
232
_eca.command(ai_add);
233
_eca.command("ao-add jack_generic," + this->output_prefix);
235
// check if filename is too long for a JACK portname.
236
// if yes, truncate filename (keep the end)
237
int max_size = jack_client_name_size(); // #include <jack/jack.h>
239
// TODO this is a workaround, and might not work on every system or with every version of jack.
240
// on OS X jack_client_name_size() returns 64, but 52 seems to be the max supported size.
241
//_client_name = "a123456789a123456789a123456789a123456789a123456789a"; // length = 51 + \0 = 52
244
max_size--; // max_size includes the terminating \0 character!
245
max_size -= 3; // to allow ecasound to append a number up to "_99"
246
max_size--; // we will add a special character at the beginning (maybe '['?)
247
_client_name = _filename;
248
assert(max_size >= 0);
249
if (_filename.size() > static_cast<size_t>(max_size))
251
_client_name = _filename.substr(_filename.size() - max_size);
252
// to visualize the truncation
253
_client_name[0] = '<';
255
// to group the inputs in an alphabetic list of clients (e.g. in qjackctl)
256
_client_name.insert(0,"["); // see max_size-- above!
257
std::replace(_client_name.begin(), _client_name.end(), '/', '_');
259
// set the name of the JACK client (using the truncated audio-filename)
260
// do not send, only receive transport msg.
261
// this must be done AFTER adding a chain
262
_eca.command("-G:jack," + _client_name + ",recv");
264
_eca.command("cs-connect");
267
throw soundfile_error("AudioPlayer::Soundfile: " + _eca.last_error());
270
// after cs-connect, get information about the input file:
271
//_eca.command("ai-get-length");
272
//_length = _eca.last_float();
273
_eca.command("ai-get-length-samples");
274
_length_samples = _eca.last_long_integer();
276
_eca.command("engine-launch");
279
_eca.command("cs-disconnect");
280
throw soundfile_error("AudioPlayer::Soundfile: " + _eca.last_error());
282
// It takes a little time until the client is available
283
// This is a little ugly, but I don't know a better way to do it.
284
// If you know one, tell me, please!
285
usleep(ssr::usleeptime);
286
VERBOSE2("Added '" + _filename
287
+ "', format: '" + apf::str::A2S(_sample_format)
288
+ "', channels: " + apf::str::A2S(_channels)
289
+ ", sample rate: " + apf::str::A2S(_sample_rate) + ".");
292
/// disconnects from ecasound.
293
AudioPlayer::Soundfile::~Soundfile()
295
// TODO: check if ecasound is really running.
296
_eca.command("cs-disconnect"); // implies "stop" and "engine-halt"
297
VERBOSE2("AudioPlayer::Soundfile: '" + _filename + "' disconnected.");
300
AudioPlayer::Soundfile::ptr_t AudioPlayer::Soundfile::create(
301
const std::string& filename, bool loop)
303
ptr_t temp; // temp = NULL
306
temp.reset(new Soundfile(filename, loop));
308
catch(soundfile_error& e)
315
int AudioPlayer::Soundfile::get_channels() const
320
std::string AudioPlayer::Soundfile::get_client_name() const
325
long int AudioPlayer::Soundfile::get_length() const
327
return _length_samples;
330
// Settings for Vim (http://www.vim.org/), please do not remove:
331
// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent
332
// vim:fdm=expr:foldexpr=getline(v\:lnum)=~'/\\*\\*'&&getline(v\:lnum)!~'\\*\\*/'?'a1'\:getline(v\:lnum)=~'\\*\\*/'&&getline(v\:lnum)!~'/\\*\\*'?'s1'\:'='