URY playd
C++ minimalist audio player
mp3.cpp
Go to the documentation of this file.
1 // This file is part of playd.
2 // playd is licensed under the MIT licence: see LICENSE.txt.
3 
10 #include <cassert>
11 #include <cstdint>
12 #include <cstdio>
13 #include <cstdlib>
14 #include <iostream>
15 #include <map>
16 #include <memory>
17 #include <sstream>
18 #include <string>
19 
20 // We don't include mpg123.h directly here, because mp3.hpp does some polyfills
21 // before including it.
22 
23 #include "../../errors.hpp"
24 #include "../../messages.h"
25 #include "../audio_source.hpp"
26 #include "../sample_formats.hpp"
27 #include "mp3.hpp"
28 
29 // This value is somewhat arbitrary, but corresponds to the minimum buffer size
30 // used by ffmpeg, so it's probably sensible.
31 const size_t Mp3AudioSource::BUFFER_SIZE = 16384;
32 
33 Mp3AudioSource::Mp3AudioSource(const std::string &path)
34  : AudioSource(path), buffer(BUFFER_SIZE), context(nullptr)
35 {
36  this->context = mpg123_new(nullptr, nullptr);
37  mpg123_format_none(this->context);
38 
39  const long *rates = nullptr;
40  size_t nrates = 0;
41  mpg123_rates(&rates, &nrates);
42  for (size_t r = 0; r < nrates; r++) {
43  Debug() << "trying to enable formats at " << rates[r]
44  << std::endl;
45  AddFormat(rates[r]);
46  }
47 
48  if (mpg123_open(this->context, path.c_str()) == MPG123_ERR) {
49  throw FileError("mp3: can't open " + path + ": " +
50  mpg123_strerror(this->context));
51  }
52 }
53 
54 Mp3AudioSource::~Mp3AudioSource()
55 {
56  mpg123_delete(this->context);
57  this->context = nullptr;
58 }
59 
60 void Mp3AudioSource::AddFormat(long rate)
61 {
62  // The requested encodings correspond to the sample formats available in
63  // the SampleFormat enum.
64  if (mpg123_format(this->context, rate, MPG123_STEREO | MPG123_MONO,
65  (MPG123_ENC_UNSIGNED_8 | MPG123_ENC_SIGNED_8 |
66  MPG123_ENC_SIGNED_16 | MPG123_ENC_SIGNED_32 |
67  MPG123_ENC_FLOAT_32)) == MPG123_ERR) {
68  // Ignore the error for now -- another sample rate may be
69  // available.
70  // If no sample rates work, loading a file will fail anyway.
71  Debug() << "can't support" << rate << std::endl;
72  };
73 }
74 
75 std::uint8_t Mp3AudioSource::ChannelCount() const
76 {
77  assert(this->context != nullptr);
78 
79  int chans = 0;
80  mpg123_getformat(this->context, nullptr, &chans, nullptr);
81  assert(chans != 0);
82  return static_cast<std::uint8_t>(chans);
83 }
84 
85 std::uint32_t Mp3AudioSource::SampleRate() const
86 {
87  assert(this->context != nullptr);
88 
89  long rate = 0;
90  mpg123_getformat(this->context, &rate, nullptr, nullptr);
91 
92  assert(0 < rate);
93  // INT32_MAX isn't a typo; if we compare against UINT32_MAX, we'll
94  // set off sign-compare errors, and the sample rate shouldn't be above
95  // INT32_MAX anyroad.
96  assert(rate <= INT32_MAX);
97  return static_cast<std::uint32_t>(rate);
98 }
99 
100 std::uint64_t Mp3AudioSource::Seek(std::uint64_t in_samples)
101 {
102  assert(this->context != nullptr);
103 
104  // Have we tried to seek past the end of the file?
105  auto clen = static_cast<unsigned long>(mpg123_length(this->context));
106  if (clen < in_samples) {
107  Debug() << "mp3: seek at" << in_samples << "past EOF at" << clen
108  << std::endl;
109  throw SeekError(MSG_SEEK_FAIL);
110  }
111 
112  if (mpg123_seek(this->context, in_samples, SEEK_SET) == MPG123_ERR) {
113  Debug() << "mp3: seek failed:" << mpg123_strerror(this->context)
114  << std::endl;
115  throw SeekError(MSG_SEEK_FAIL);
116  }
117 
118  // The actual seek position may not be the same as the requested
119  // position.
120  // mpg123_tell gives us the exact mono-samples position.
121  return mpg123_tell(this->context);
122 }
123 
124 Mp3AudioSource::DecodeResult Mp3AudioSource::Decode()
125 {
126  assert(this->context != nullptr);
127 
128  auto buf = reinterpret_cast<unsigned char *>(&this->buffer.front());
129  size_t rbytes = 0;
130  int err = mpg123_read(this->context, buf, this->buffer.size(), &rbytes);
131 
132  DecodeVector decoded;
133  DecodeState decode_state;
134 
135  if (err == MPG123_DONE) {
136  decode_state = DecodeState::END_OF_FILE;
137  } else if (err != MPG123_OK && err != MPG123_NEW_FORMAT) {
138  Debug() << "mp3: decode error:" << mpg123_strerror(this->context)
139  << std::endl;
140  decode_state = DecodeState::END_OF_FILE;
141  } else {
142  decode_state = DecodeState::DECODING;
143 
144  // Copy only the bit of the buffer occupied by decoded data
145  auto front = this->buffer.begin();
146  decoded = DecodeVector(front, front + rbytes);
147  }
148 
149  return std::make_pair(decode_state, decoded);
150 }
151 
152 SampleFormat Mp3AudioSource::OutputSampleFormat() const
153 {
154  assert(this->context != nullptr);
155 
156  int encoding = 0;
157  mpg123_getformat(this->context, nullptr, nullptr, &encoding);
158 
159  switch (encoding) {
160  case MPG123_ENC_UNSIGNED_8:
162  case MPG123_ENC_SIGNED_8:
164  case MPG123_ENC_SIGNED_16:
166  case MPG123_ENC_SIGNED_32:
168  case MPG123_ENC_FLOAT_32:
170  default:
171  // We shouldn't get here, if the format was set up
172  // correctly earlier.
173  throw InternalError(
174  "unsupported sample rate, should not "
175  "happen");
176  }
177 }
An object responsible for decoding an audio file.
Packed 8-bit unsigned integer.
Packed 16-bit signed integer.
Declaration of the Mp3AudioSource class.
An Error signifying that playd can&#39;t read a file.
Definition: errors.hpp:75
Packed 32-bit signed integer.
const std::string MSG_SEEK_FAIL
Message shown when an attempt to seek fails.
Definition: messages.h:95
Packed 8-bit signed integer.
An Error signifying that playd can&#39;t seek in a file.
Definition: errors.hpp:90
SampleFormat
Sample formats available in playd.
Class for telling the human what playd is doing.
Definition: errors.hpp:133
An Error signifying that playd has hit an internal snag.
Definition: errors.hpp:60
Packed 32-bit floating point.