~ubuntu-branches/ubuntu/utopic/mpd/utopic-proposed

« back to all changes in this revision

Viewing changes to src/decoder/OpusDecoderPlugin.cxx

  • Committer: Package Import Robot
  • Author(s): Steve Kowalik
  • Date: 2013-11-12 18:17:40 UTC
  • mfrom: (2.2.36 sid)
  • Revision ID: package-import@ubuntu.com-20131112181740-72aa4zihehoobedp
Tags: 0.18.3-1ubuntu1
* Merge from Debian unstable.  Remaining changes:
  - Add libmp3lame-dev to Build-Depends, and enable LAME.
  - Read the user for the daemon from the config file in the init script.
  - Move avahi-daemon from Suggests to Recommends.
  - Added apport hook to include user configuration file.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * Copyright (C) 2003-2013 The Music Player Daemon Project
 
3
 * http://www.musicpd.org
 
4
 *
 
5
 * This program is free software; you can redistribute it and/or modify
 
6
 * it under the terms of the GNU General Public License as published by
 
7
 * the Free Software Foundation; either version 2 of the License, or
 
8
 * (at your option) any later version.
 
9
 *
 
10
 * This program is distributed in the hope that it will be useful,
 
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
13
 * GNU General Public License for more details.
 
14
 *
 
15
 * You should have received a copy of the GNU General Public License along
 
16
 * with this program; if not, write to the Free Software Foundation, Inc.,
 
17
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 
18
 */
 
19
 
 
20
#include "config.h" /* must be first for large file support */
 
21
#include "OpusDecoderPlugin.h"
 
22
#include "OpusDomain.hxx"
 
23
#include "OpusHead.hxx"
 
24
#include "OpusTags.hxx"
 
25
#include "OggUtil.hxx"
 
26
#include "OggFind.hxx"
 
27
#include "OggSyncState.hxx"
 
28
#include "DecoderAPI.hxx"
 
29
#include "OggCodec.hxx"
 
30
#include "CheckAudioFormat.hxx"
 
31
#include "tag/TagHandler.hxx"
 
32
#include "tag/TagBuilder.hxx"
 
33
#include "InputStream.hxx"
 
34
#include "util/Error.hxx"
 
35
#include "Log.hxx"
 
36
 
 
37
#include <opus.h>
 
38
#include <ogg/ogg.h>
 
39
 
 
40
#include <glib.h>
 
41
 
 
42
#include <string.h>
 
43
 
 
44
static constexpr opus_int32 opus_sample_rate = 48000;
 
45
 
 
46
gcc_pure
 
47
static bool
 
48
IsOpusHead(const ogg_packet &packet)
 
49
{
 
50
        return packet.bytes >= 8 && memcmp(packet.packet, "OpusHead", 8) == 0;
 
51
}
 
52
 
 
53
gcc_pure
 
54
static bool
 
55
IsOpusTags(const ogg_packet &packet)
 
56
{
 
57
        return packet.bytes >= 8 && memcmp(packet.packet, "OpusTags", 8) == 0;
 
58
}
 
59
 
 
60
static bool
 
61
mpd_opus_init(gcc_unused const config_param &param)
 
62
{
 
63
        LogDebug(opus_domain, opus_get_version_string());
 
64
 
 
65
        return true;
 
66
}
 
67
 
 
68
class MPDOpusDecoder {
 
69
        Decoder &decoder;
 
70
        InputStream &input_stream;
 
71
 
 
72
        ogg_stream_state os;
 
73
 
 
74
        OpusDecoder *opus_decoder;
 
75
        opus_int16 *output_buffer;
 
76
        unsigned output_size;
 
77
 
 
78
        bool os_initialized;
 
79
        bool found_opus;
 
80
 
 
81
        int opus_serialno;
 
82
 
 
83
        ogg_int64_t eos_granulepos;
 
84
 
 
85
        size_t frame_size;
 
86
 
 
87
public:
 
88
        MPDOpusDecoder(Decoder &_decoder,
 
89
                       InputStream &_input_stream)
 
90
                :decoder(_decoder), input_stream(_input_stream),
 
91
                 opus_decoder(nullptr),
 
92
                 output_buffer(nullptr), output_size(0),
 
93
                 os_initialized(false), found_opus(false) {}
 
94
        ~MPDOpusDecoder();
 
95
 
 
96
        bool ReadFirstPage(OggSyncState &oy);
 
97
        bool ReadNextPage(OggSyncState &oy);
 
98
 
 
99
        DecoderCommand HandlePackets();
 
100
        DecoderCommand HandlePacket(const ogg_packet &packet);
 
101
        DecoderCommand HandleBOS(const ogg_packet &packet);
 
102
        DecoderCommand HandleTags(const ogg_packet &packet);
 
103
        DecoderCommand HandleAudio(const ogg_packet &packet);
 
104
 
 
105
        bool Seek(OggSyncState &oy, double where);
 
106
};
 
107
 
 
108
MPDOpusDecoder::~MPDOpusDecoder()
 
109
{
 
110
        g_free(output_buffer);
 
111
 
 
112
        if (opus_decoder != nullptr)
 
113
                opus_decoder_destroy(opus_decoder);
 
114
 
 
115
        if (os_initialized)
 
116
                ogg_stream_clear(&os);
 
117
}
 
118
 
 
119
inline bool
 
120
MPDOpusDecoder::ReadFirstPage(OggSyncState &oy)
 
121
{
 
122
        assert(!os_initialized);
 
123
 
 
124
        if (!oy.ExpectFirstPage(os))
 
125
                return false;
 
126
 
 
127
        os_initialized = true;
 
128
        return true;
 
129
}
 
130
 
 
131
inline bool
 
132
MPDOpusDecoder::ReadNextPage(OggSyncState &oy)
 
133
{
 
134
        assert(os_initialized);
 
135
 
 
136
        ogg_page page;
 
137
        if (!oy.ExpectPage(page))
 
138
                return false;
 
139
 
 
140
        const auto page_serialno = ogg_page_serialno(&page);
 
141
        if (page_serialno != os.serialno)
 
142
                ogg_stream_reset_serialno(&os, page_serialno);
 
143
 
 
144
        ogg_stream_pagein(&os, &page);
 
145
        return true;
 
146
}
 
147
 
 
148
inline DecoderCommand
 
149
MPDOpusDecoder::HandlePackets()
 
150
{
 
151
        ogg_packet packet;
 
152
        while (ogg_stream_packetout(&os, &packet) == 1) {
 
153
                auto cmd = HandlePacket(packet);
 
154
                if (cmd != DecoderCommand::NONE)
 
155
                        return cmd;
 
156
        }
 
157
 
 
158
        return DecoderCommand::NONE;
 
159
}
 
160
 
 
161
inline DecoderCommand
 
162
MPDOpusDecoder::HandlePacket(const ogg_packet &packet)
 
163
{
 
164
        if (packet.e_o_s)
 
165
                return DecoderCommand::STOP;
 
166
 
 
167
        if (packet.b_o_s)
 
168
                return HandleBOS(packet);
 
169
        else if (!found_opus)
 
170
                return DecoderCommand::STOP;
 
171
 
 
172
        if (IsOpusTags(packet))
 
173
                return HandleTags(packet);
 
174
 
 
175
        return HandleAudio(packet);
 
176
}
 
177
 
 
178
/**
 
179
 * Load the end-of-stream packet and restore the previous file
 
180
 * position.
 
181
 */
 
182
static bool
 
183
LoadEOSPacket(InputStream &is, Decoder *decoder, int serialno,
 
184
              ogg_packet &packet)
 
185
{
 
186
        if (!is.CheapSeeking())
 
187
                /* we do this for local files only, because seeking
 
188
                   around remote files is expensive and not worth the
 
189
                   troubl */
 
190
                return -1;
 
191
 
 
192
        const auto old_offset = is.offset;
 
193
        if (old_offset < 0)
 
194
                return -1;
 
195
 
 
196
        /* create temporary Ogg objects for seeking and parsing the
 
197
           EOS packet */
 
198
        OggSyncState oy(is, decoder);
 
199
        ogg_stream_state os;
 
200
        ogg_stream_init(&os, serialno);
 
201
 
 
202
        bool result = OggSeekFindEOS(oy, os, packet, is);
 
203
        ogg_stream_clear(&os);
 
204
 
 
205
        /* restore the previous file position */
 
206
        is.Seek(old_offset, SEEK_SET, IgnoreError());
 
207
 
 
208
        return result;
 
209
}
 
210
 
 
211
/**
 
212
 * Load the end-of-stream granulepos and restore the previous file
 
213
 * position.
 
214
 *
 
215
 * @return -1 on error
 
216
 */
 
217
gcc_pure
 
218
static ogg_int64_t
 
219
LoadEOSGranulePos(InputStream &is, Decoder *decoder, int serialno)
 
220
{
 
221
        ogg_packet packet;
 
222
        if (!LoadEOSPacket(is, decoder, serialno, packet))
 
223
                return -1;
 
224
 
 
225
        return packet.granulepos;
 
226
}
 
227
 
 
228
inline DecoderCommand
 
229
MPDOpusDecoder::HandleBOS(const ogg_packet &packet)
 
230
{
 
231
        assert(packet.b_o_s);
 
232
 
 
233
        if (found_opus || !IsOpusHead(packet))
 
234
                return DecoderCommand::STOP;
 
235
 
 
236
        unsigned channels;
 
237
        if (!ScanOpusHeader(packet.packet, packet.bytes, channels) ||
 
238
            !audio_valid_channel_count(channels))
 
239
                return DecoderCommand::STOP;
 
240
 
 
241
        assert(opus_decoder == nullptr);
 
242
        assert(output_buffer == nullptr);
 
243
 
 
244
        opus_serialno = os.serialno;
 
245
        found_opus = true;
 
246
 
 
247
        /* TODO: parse attributes from the OpusHead (sample rate,
 
248
           channels, ...) */
 
249
 
 
250
        int opus_error;
 
251
        opus_decoder = opus_decoder_create(opus_sample_rate, channels,
 
252
                                           &opus_error);
 
253
        if (opus_decoder == nullptr) {
 
254
                FormatError(opus_domain, "libopus error: %s",
 
255
                            opus_strerror(opus_error));
 
256
                return DecoderCommand::STOP;
 
257
        }
 
258
 
 
259
        eos_granulepos = LoadEOSGranulePos(input_stream, &decoder,
 
260
                                           opus_serialno);
 
261
        const double duration = eos_granulepos >= 0
 
262
                ? double(eos_granulepos) / opus_sample_rate
 
263
                : -1.0;
 
264
 
 
265
        const AudioFormat audio_format(opus_sample_rate,
 
266
                                       SampleFormat::S16, channels);
 
267
        decoder_initialized(decoder, audio_format,
 
268
                            eos_granulepos > 0, duration);
 
269
        frame_size = audio_format.GetFrameSize();
 
270
 
 
271
        /* allocate an output buffer for 16 bit PCM samples big enough
 
272
           to hold a quarter second, larger than 120ms required by
 
273
           libopus */
 
274
        output_size = audio_format.sample_rate / 4;
 
275
        output_buffer = (opus_int16 *)
 
276
                g_malloc(sizeof(*output_buffer) * output_size *
 
277
                         audio_format.channels);
 
278
 
 
279
        return decoder_get_command(decoder);
 
280
}
 
281
 
 
282
inline DecoderCommand
 
283
MPDOpusDecoder::HandleTags(const ogg_packet &packet)
 
284
{
 
285
        ReplayGainInfo rgi;
 
286
        rgi.Clear();
 
287
 
 
288
        TagBuilder tag_builder;
 
289
 
 
290
        DecoderCommand cmd;
 
291
        if (ScanOpusTags(packet.packet, packet.bytes,
 
292
                         &rgi,
 
293
                         &add_tag_handler, &tag_builder) &&
 
294
            !tag_builder.IsEmpty()) {
 
295
                decoder_replay_gain(decoder, &rgi);
 
296
 
 
297
                Tag tag;
 
298
                tag_builder.Commit(tag);
 
299
                cmd = decoder_tag(decoder, input_stream, std::move(tag));
 
300
        } else
 
301
                cmd = decoder_get_command(decoder);
 
302
 
 
303
        return cmd;
 
304
}
 
305
 
 
306
inline DecoderCommand
 
307
MPDOpusDecoder::HandleAudio(const ogg_packet &packet)
 
308
{
 
309
        assert(opus_decoder != nullptr);
 
310
 
 
311
        int nframes = opus_decode(opus_decoder,
 
312
                                  (const unsigned char*)packet.packet,
 
313
                                  packet.bytes,
 
314
                                  output_buffer, output_size,
 
315
                                  0);
 
316
        if (nframes < 0) {
 
317
                LogError(opus_domain, opus_strerror(nframes));
 
318
                return DecoderCommand::STOP;
 
319
        }
 
320
 
 
321
        if (nframes > 0) {
 
322
                const size_t nbytes = nframes * frame_size;
 
323
                auto cmd = decoder_data(decoder, input_stream,
 
324
                                        output_buffer, nbytes,
 
325
                                        0);
 
326
                if (cmd != DecoderCommand::NONE)
 
327
                        return cmd;
 
328
 
 
329
                if (packet.granulepos > 0)
 
330
                        decoder_timestamp(decoder,
 
331
                                          double(packet.granulepos)
 
332
                                          / opus_sample_rate);
 
333
        }
 
334
 
 
335
        return DecoderCommand::NONE;
 
336
}
 
337
 
 
338
bool
 
339
MPDOpusDecoder::Seek(OggSyncState &oy, double where_s)
 
340
{
 
341
        assert(eos_granulepos > 0);
 
342
        assert(input_stream.seekable);
 
343
        assert(input_stream.size > 0);
 
344
        assert(input_stream.offset >= 0);
 
345
 
 
346
        const ogg_int64_t where_granulepos(where_s * opus_sample_rate);
 
347
 
 
348
        /* interpolate the file offset where we expect to find the
 
349
           given granule position */
 
350
        /* TODO: implement binary search */
 
351
        InputStream::offset_type offset(where_granulepos * input_stream.size
 
352
                                        / eos_granulepos);
 
353
 
 
354
        if (!OggSeekPageAtOffset(oy, os, input_stream, offset, SEEK_SET))
 
355
                return false;
 
356
 
 
357
        decoder_timestamp(decoder, where_s);
 
358
        return true;
 
359
}
 
360
 
 
361
static void
 
362
mpd_opus_stream_decode(Decoder &decoder,
 
363
                       InputStream &input_stream)
 
364
{
 
365
        if (ogg_codec_detect(&decoder, input_stream) != OGG_CODEC_OPUS)
 
366
                return;
 
367
 
 
368
        /* rewind the stream, because ogg_codec_detect() has
 
369
           moved it */
 
370
        input_stream.LockRewind(IgnoreError());
 
371
 
 
372
        MPDOpusDecoder d(decoder, input_stream);
 
373
        OggSyncState oy(input_stream, &decoder);
 
374
 
 
375
        if (!d.ReadFirstPage(oy))
 
376
                return;
 
377
 
 
378
        while (true) {
 
379
                auto cmd = d.HandlePackets();
 
380
                if (cmd == DecoderCommand::SEEK) {
 
381
                        if (d.Seek(oy, decoder_seek_where(decoder)))
 
382
                                decoder_command_finished(decoder);
 
383
                        else
 
384
                                decoder_seek_error(decoder);
 
385
 
 
386
                        continue;
 
387
                }
 
388
 
 
389
                if (cmd != DecoderCommand::NONE)
 
390
                        break;
 
391
 
 
392
                if (!d.ReadNextPage(oy))
 
393
                        break;
 
394
        }
 
395
}
 
396
 
 
397
static bool
 
398
mpd_opus_scan_stream(InputStream &is,
 
399
                     const struct tag_handler *handler, void *handler_ctx)
 
400
{
 
401
        OggSyncState oy(is);
 
402
 
 
403
        ogg_stream_state os;
 
404
        if (!oy.ExpectFirstPage(os))
 
405
                return false;
 
406
 
 
407
        /* read at most two more pages */
 
408
        unsigned remaining_pages = 2;
 
409
 
 
410
        bool result = false;
 
411
 
 
412
        ogg_packet packet;
 
413
        while (true) {
 
414
                int r = ogg_stream_packetout(&os, &packet);
 
415
                if (r < 0) {
 
416
                        result = false;
 
417
                        break;
 
418
                }
 
419
 
 
420
                if (r == 0) {
 
421
                        if (remaining_pages-- == 0)
 
422
                                break;
 
423
 
 
424
                        if (!oy.ExpectPageIn(os)) {
 
425
                                result = false;
 
426
                                break;
 
427
                        }
 
428
 
 
429
                        continue;
 
430
                }
 
431
 
 
432
                if (packet.b_o_s) {
 
433
                        if (!IsOpusHead(packet))
 
434
                                break;
 
435
 
 
436
                        unsigned channels;
 
437
                        if (!ScanOpusHeader(packet.packet, packet.bytes, channels) ||
 
438
                            !audio_valid_channel_count(channels)) {
 
439
                                result = false;
 
440
                                break;
 
441
                        }
 
442
 
 
443
                        result = true;
 
444
                } else if (!result)
 
445
                        break;
 
446
                else if (IsOpusTags(packet)) {
 
447
                        if (!ScanOpusTags(packet.packet, packet.bytes,
 
448
                                          nullptr,
 
449
                                          handler, handler_ctx))
 
450
                                result = false;
 
451
 
 
452
                        break;
 
453
                }
 
454
        }
 
455
 
 
456
        if (packet.e_o_s || OggSeekFindEOS(oy, os, packet, is))
 
457
                tag_handler_invoke_duration(handler, handler_ctx,
 
458
                                            packet.granulepos / opus_sample_rate);
 
459
 
 
460
        ogg_stream_clear(&os);
 
461
 
 
462
        return result;
 
463
}
 
464
 
 
465
static const char *const opus_suffixes[] = {
 
466
        "opus",
 
467
        "ogg",
 
468
        "oga",
 
469
        nullptr
 
470
};
 
471
 
 
472
static const char *const opus_mime_types[] = {
 
473
        "audio/opus",
 
474
        nullptr
 
475
};
 
476
 
 
477
const struct DecoderPlugin opus_decoder_plugin = {
 
478
        "opus",
 
479
        mpd_opus_init,
 
480
        nullptr,
 
481
        mpd_opus_stream_decode,
 
482
        nullptr,
 
483
        nullptr,
 
484
        mpd_opus_scan_stream,
 
485
        nullptr,
 
486
        opus_suffixes,
 
487
        opus_mime_types,
 
488
};