summaryrefslogtreecommitdiffstats
path: root/src/encoder/encoderwave.cpp
blob: 3bdec355d429c00182a7284dcdb8865fe85a5528 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
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
211
212
213
214
215
216
217
218
219
220
221
222
#include <QtDebug>

#include "encoder/encoderwave.h"

#include "encoder/encodercallback.h"
#include "errordialoghandler.h"
#include "recording/defs_recording.h"


// The virtual file context must return the length of the virtual file in bytes.
static sf_count_t  sf_f_get_filelen (void *user_data)
{
    EncoderCallback* pCallback = static_cast<EncoderCallback*>(user_data);
    return pCallback->filelen();
}
// The virtual file context must seek to offset using the seek mode provided
// by whence which is one of
//      SEEK_CUR
//      SEEK_SET
//      SEEK_END
// The return value must contain the new offset in the file.
static sf_count_t  sf_f_seek (sf_count_t offset, int whence, void *user_data)
{
    sf_count_t new_offset;
    EncoderCallback* pCallback = static_cast<EncoderCallback*>(user_data);
    if (whence == SEEK_SET) {
        new_offset = offset;
        pCallback->seek(static_cast<int>(new_offset));
    } else if (whence == SEEK_CUR) {
        new_offset = pCallback->tell()+offset;
        pCallback->seek(static_cast<int>(new_offset));
    } else {
        new_offset =  pCallback->filelen()-offset;
        pCallback->seek(static_cast<int>(new_offset));
    }
    return new_offset;
}
// The virtual file context must copy ("read") "count" bytes into the buffer
// provided by ptr and return the count of actually copied bytes.
static sf_count_t  sf_f_read (void *ptr, sf_count_t count, void *user_data)
{
    Q_UNUSED(ptr);
    Q_UNUSED(count);
    Q_UNUSED(user_data);
    qWarning() << "sf_f_read called for EncoderWave. Call not implemented!";
    return 0;
}

// The virtual file context must process "count" bytes stored in the buffer passed
//  with ptr and return the count of actually processed bytes.
static sf_count_t sf_f_write (const void *ptr, sf_count_t count, void *user_data)
{
    EncoderCallback* pCallback = static_cast<EncoderCallback*>(user_data);
    pCallback->write(nullptr, static_cast<const unsigned char*>(ptr), 0, static_cast<int>(count));
    return count;
}

// Return the current position of the virtual file context.
static sf_count_t  sf_f_tell (void *user_data)
{
    EncoderCallback* pCallback = static_cast<EncoderCallback*>(user_data);
    return pCallback->tell();
}




EncoderWave::EncoderWave(EncoderCallback* pCallback)
        : m_pCallback(pCallback),
          m_pSndfile(nullptr) {
    m_sfInfo.frames = 0;
    m_sfInfo.samplerate = 0;
    m_sfInfo.channels = 0;
    m_sfInfo.format = 0;
    m_sfInfo.sections = 0;
    m_sfInfo.seekable = 0;

    // Libsndfile calls the callbacks provided by the SF_VIRTUAL_IO structure
    // when opening, reading and writing to the virtual file context. The user_data
    // pointer is a user defined context which will be available in the callbacks.
    m_virtualIo.get_filelen = sf_f_get_filelen;
    m_virtualIo.seek = sf_f_seek;
    m_virtualIo.read = sf_f_read;
    m_virtualIo.write = sf_f_write;
    m_virtualIo.tell = sf_f_tell;
}

EncoderWave::~EncoderWave() {
    if (m_pSndfile != nullptr) {
        sf_close(m_pSndfile);
    }
}

void EncoderWave::setEncoderSettings(const EncoderSettings& settings) {
    const EncoderWaveSettings& wavesettings = reinterpret_cast<const EncoderWaveSettings&>(settings);
    QString format = wavesettings.getFormat();
    if (format == ENCODING_WAVE) {
        m_sfInfo.format = SF_FORMAT_WAV;
    } else if (format == ENCODING_AIFF) {
        m_sfInfo.format = SF_FORMAT_AIFF;
    } else {
        qWarning() << "Unexpected Format when setting EncoderWave: " << format << ". Reverting to wav";
        // Other possibly interesting formats
        // There is a n option for RF64 to automatically downgrade to RIFF WAV if less than 4GB using an
        // sf_command, so it could be interesting to use it in place of FORMAT_WAVE.
        // SF_FORMAT_W64          = 0x0B0000,     /* Sonic Foundry's 64 bit RIFF/WAV */
        // SF_FORMAT_RF64         = 0x220000,     /* RF64 WAV file */

        // I guess this one is WAVEFORMATEXTENSIBLE, not WAVEFORMATEX.
        // Not really useful for us since it's mostly for multichannel setups.
        // SF_FORMAT_WAVEX        = 0x130000,     /* MS WAVE with WAVEFORMATEX */

        // SF_FORMAT_CAF          = 0x180000,     /* Core Audio File format */
    }
    int radio = settings.getSelectedOption(EncoderWaveSettings::BITS_GROUP);
    switch(radio) {
        case 0:
            m_sfInfo.format |= SF_FORMAT_PCM_16;
            break;
        case 1:
            m_sfInfo.format |= SF_FORMAT_PCM_24;
            break;
        case 2:
            m_sfInfo.format |= SF_FORMAT_FLOAT;
            break;
        default:
            m_sfInfo.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;
            qWarning() << " Unexpected radio index on EncoderWave: "
                    << radio << ". reverting to PCM 16bits";
            break;
    }
}

// call sendPackages() or write() after 'flush()' as outlined in enginebroadcast.cpp
void EncoderWave::flush() {
    sf_write_sync(m_pSndfile);
}


void EncoderWave::encodeBuffer(const CSAMPLE *pBuffer, const int iBufferSize) {
    sf_write_float(m_pSndfile, pBuffer, iBufferSize);
}

/* Originally called from enginebroadcast.cpp to update metadata information
 * when streaming, however, this causes pops
 *
 * Currently this method is used before init() once to save artist, title and album
*/
void EncoderWave::updateMetaData(const QString& artist, const QString& title, const QString& album) {
    m_metaDataTitle = title;
    m_metaDataArtist = artist;
    m_metaDataAlbum = album;
}

void EncoderWave::initStream() {

    // Tell the encoder to automatically convert float input range to the correct output range.
    sf_command(m_pSndfile, SFC_SET_NORM_FLOAT, nullptr, SF_TRUE);
    // Tell the encoder that, when converting to integer formats, clip
    // automatically the values that go outside of the allowed range.
    // Warning! Depending on how libsndfile is compiled autoclip may not work.
    // Ensure CPU_CLIPS_NEGATIVE and CPU_CLIPS_POSITIVE is setup properly in the build.
    sf_command(m_pSndfile, SFC_SET_CLIPPING, nullptr, SF_TRUE);

    // Strings passed to and retrieved from sf_get_string/sf_set_string are assumed to be utf-8.
    // However, while formats like Ogg/Vorbis and FLAC fully support utf-8, others like WAV and
    // AIFF officially only support ASCII. Writing utf-8 strings to WAV and AIF files with
    // libsndfile will work when read back with libsndfile, but may not work with other programs.
    int ret;
    if (!m_metaDataTitle.isEmpty()) {
        ret = sf_set_string(m_pSndfile, SF_STR_TITLE, m_metaDataTitle.toUtf8().constData());
        if (ret != 0) {
            qWarning("libsndfile error: %s", sf_error_number(ret));
        }
    }

    if (!m_metaDataArtist.isEmpty()) {
        ret = sf_set_string(m_pSndfile, SF_STR_ARTIST, m_metaDataArtist.toUtf8().constData());
        if (ret != 0) {
            qWarning("libsndfile error: %s", sf_error_number(ret));
        }
    }
    if (!m_metaDataAlbum.isEmpty()) {
        int strType = SF_STR_ALBUM;
        if (m_sfInfo.format == SF_FORMAT_AIFF) {
            // There is no AIFF text chunk for "Album". But libsndfile is able to
            // write the SF_STR_COMMENT string into the text chunk with id "ANNO".
            strType = SF_STR_COMMENT;
        }
        ret = sf_set_string(m_pSndfile, strType, m_metaDataAlbum.toUtf8().constData());
        if (ret != 0) {
            qWarning("libsndfile error: %s", sf_error_number(ret));
        }
    }
}

int EncoderWave::initEncoder(mixxx::audio::SampleRate sampleRate, QString* pUserErrorMessage) {
    Q_UNUSED(pUserErrorMessage);
    // set sfInfo.
    // m_sfInfo.format is setup on setEncoderSettings previous to calling initEncoder.
    m_sfInfo.samplerate = sampleRate;
    m_sfInfo.channels = 2;
    m_sfInfo.frames = 0;
    m_sfInfo.sections = 0;
    m_sfInfo.seekable = 0;

    // Opens a soundfile from a virtual file I/O context which is provided by the caller.
    // This is usually used to interface libsndfile to a stream or buffer based system.
    // Apart from the sfvirtual and the user_data parameters this function behaves like sf_open.
    m_pSndfile = sf_open_virtual (&m_virtualIo, SFM_WRITE, &m_sfInfo, m_pCallback) ;

    int ret=0;
    if (m_pSndfile == nullptr) {
        qDebug()
                << "Error initializing Wave encoding. sf_open_virtual returned:"
                << sf_strerror(nullptr);
        ret = -1;
    } else {
        initStream();
    };
    return ret;
}