Skip to content

Commit b0862b8

Browse files
committed
Add speex audio codec support
1 parent 2723374 commit b0862b8

11 files changed

Lines changed: 371 additions & 3 deletions

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,6 @@
2828
[submodule "libraries/universal-dash-transmuxer"]
2929
path = libraries/universal-dash-transmuxer
3030
url = https://github.com/google/universal-dash-transmuxer.git
31+
[submodule "libraries/speex"]
32+
path = libraries/speex
33+
url = https://github.com/xiph/speex.git

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ The interfaces are as follows:
3535
- [x] AAC
3636
- [x] Ogg Opus
3737
- [x] FLAC
38+
- [x] speex
3839

3940
## Dependencies :globe_with_meridians:
4041

@@ -48,8 +49,10 @@ The interfaces are as follows:
4849
- [opus](https://github.com/xiph/opus)
4950
- [opusfile](https://github.com/xiph/opusfile)
5051
- [protobuf](https://developers.google.com/protocol-buffers/)
52+
- [speex](https://github.com/xiph/speex)
5153

5254
## Installation :inbox_tray:
55+
5356
`NFDecoder` is a [Cmake](https://cmake.org/) project, while you are free to download the prebuilt static libraries it is recommended to use Cmake to install this project into your wider project. In order to add this into a wider Cmake project (who needs monorepos anyway?), simply add the following line to your `CMakeLists.txt` file:
5457
```
5558
add_subdirectory(NFDecoder)

include/NFDecoder/NFDecoderMimeTypes.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,5 +59,10 @@ extern const std::set<std::string> NF_DECODER_MP3_MIME_TYPES;
5959
extern const std::string NF_DECODER_MIME_TYPE_MIDI;
6060
extern const std::set<std::string> NF_DECODER_MIDI_MIME_TYPES;
6161

62+
// SPEEX
63+
extern const std::string NF_DECODER_MIME_TYPE_SPEEX_OGG;
64+
extern const std::string NF_DECODER_MIME_TYPE_SPEEX;
65+
extern const std::set<std::string> NF_DECODER_SPEEX_MIME_TYPES;
66+
6267
} // namespace decoder
6368
} // namespace nativeformat

libraries/CMakeLists.txt

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -596,3 +596,93 @@ set(TINYSOUNDFONT_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/TinySoundFont)
596596
add_library(TinySoundFont INTERFACE)
597597
target_include_directories(TinySoundFont INTERFACE ${TINYSOUNDFONT_INCLUDE_DIR})
598598

599+
# speex
600+
set(SPEEX_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/speex/include CACHE STRING "SPEEX includes" FORCE)
601+
set(SPEEX_SOURCE_FILES
602+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/bits.c
603+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/cb_search.c
604+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/exc_5_64_table.c
605+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/exc_5_256_table.c
606+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/exc_8_128_table.c
607+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/exc_10_16_table.c
608+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/exc_10_32_table.c
609+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/exc_20_32_table.c
610+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/fftwrap.c
611+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/filters.c
612+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/gain_table_lbr.c
613+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/gain_table.c
614+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/hexc_10_32_table.c
615+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/hexc_table.c
616+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/high_lsp_tables.c
617+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/kiss_fft.c
618+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/kiss_fftr.c
619+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/lpc.c
620+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/lsp_tables_nb.c
621+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/lsp.c
622+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/ltp.c
623+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/modes_wb.c
624+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/modes.c
625+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/nb_celp.c
626+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/quant_lsp.c
627+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/sb_celp.c
628+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/smallft.c
629+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/speex_callbacks.c
630+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/speex.c
631+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/stereo.c
632+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/vbr.c
633+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/vorbis_psy.c
634+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/vq.c
635+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/window.c)
636+
set(SPEEX_INCLUDE_FILES
637+
${CMAKE_CURRENT_SOURCE_DIR}/speex/include/speex/speex_bits.h
638+
${CMAKE_CURRENT_SOURCE_DIR}/speex/include/speex/speex_callbacks.h
639+
${CMAKE_CURRENT_SOURCE_DIR}/speex/include/speex/speex_header.h
640+
${CMAKE_CURRENT_SOURCE_DIR}/speex/include/speex/speex_stereo.h
641+
${CMAKE_CURRENT_SOURCE_DIR}/speex/include/speex/speex_types.h
642+
${CMAKE_CURRENT_SOURCE_DIR}/speex/include/speex/speex.h
643+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/_kiss_fft_guts.h
644+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/arch.h
645+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/bfin.h
646+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/cb_search_arm4.h
647+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/cb_search_bfin.h
648+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/cb_search_sse.h
649+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/cb_search.h
650+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/fftwrap.h
651+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/filters_arm4.h
652+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/filters_bfin.h
653+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/filters_sse.h
654+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/filters.h
655+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/fixed_arm4.h
656+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/fixed_arm5e.h
657+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/fixed_bfin.h
658+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/fixed_debug.h
659+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/fixed_generic.h
660+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/kiss_fft.h
661+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/kiss_fftr.h
662+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/lpc_bfin.h
663+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/lpc.h
664+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/lsp_bfin.h
665+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/lsp.h
666+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/ltp_arm4.h
667+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/ltp_bfin.h
668+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/ltp_sse.h
669+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/ltp.h
670+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/math_approx.h
671+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/misc_bfin.h
672+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/modes.h
673+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/nb_celp.h
674+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/os_support.h
675+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/quant_lsp_bfin.h
676+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/sb_celp.h
677+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/smallft.h
678+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/stack_alloc.h
679+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/vbr.h
680+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/vorbis_psy.h
681+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/vq_arm4.h
682+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/vq_bfin.h
683+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/vq_sse.h
684+
${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex/vq.h)
685+
add_library(speex STATIC ${SPEEX_SOURCE_FILES} ${SPEEX_INCLUDE_FILES})
686+
target_include_directories(speex PUBLIC ${SPEEX_INCLUDE_DIR} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/speex/libspeex)
687+
target_compile_definitions(speex PRIVATE FIXED_POINT=1 EXPORT=__attribute__\(\(visibility\(\"default\"\)\)\) USE_SMALLFT=1)
688+
target_compile_options(opus PRIVATE "-Wno-unused-parameter" "-Wno-shadow")

libraries/speex

Submodule speex added at 870ff84

source/CMakeLists.txt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,9 @@ set(SOURCE_FILES
6969
DecoderAndroidImplementation.h
7070
DecoderAndroidImplementation.cpp
7171
FactoryAndroidImplementation.h
72-
FactoryAndroidImplementation.cpp)
72+
FactoryAndroidImplementation.cpp
73+
DecoderSpeexImplementation.h
74+
DecoderSpeexImplementation.cpp)
7375
set(LINK_LIBRARIES
7476
ogg
7577
vorbis
@@ -81,6 +83,7 @@ set(LINK_LIBRARIES
8183
${OPENSSL_LIBRARIES}
8284
flac
8385
TinySoundFont
86+
speex
8487
)
8588
set(INCLUDE_DIRS
8689
../include
@@ -90,6 +93,7 @@ set(INCLUDE_DIRS
9093
../libraries/ogg/include
9194
../libraries/NFHTTP/include
9295
../libraries/universal-dash-transmuxer/include
96+
../libraries/speex/include
9397
${CMAKE_BINARY_DIR}/output)
9498

9599
if(INCLUDE_UDT)

source/DecoderNormalisationImplementation.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,11 @@ void DecoderNormalisationImplementation::seek(long frame_index) {
105105
}
106106

107107
long DecoderNormalisationImplementation::frames() {
108-
return _wrapped_decoder->frames() * _factor;
108+
const auto wrapped_frames = _wrapped_decoder->frames();
109+
if (wrapped_frames == UNKNOWN_FRAMES) {
110+
return UNKNOWN_FRAMES;
111+
}
112+
return wrapped_frames * _factor;
109113
}
110114

111115
void DecoderNormalisationImplementation::decode(long frames,
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
/*
2+
* Copyright (c) 2021 Spotify AB.
3+
*
4+
* Licensed to the Apache Software Foundation (ASF) under one
5+
* or more contributor license agreements. See the NOTICE file
6+
* distributed with this work for additional information
7+
* regarding copyright ownership. The ASF licenses this file
8+
* to you under the Apache License, Version 2.0 (the
9+
* "License"); you may not use this file except in compliance
10+
* with the License. You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing,
15+
* software distributed under the License is distributed on an
16+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17+
* KIND, either express or implied. See the License for the
18+
* specific language governing permissions and limitations
19+
* under the License.
20+
*/
21+
#include "DecoderSpeexImplementation.h"
22+
23+
#include <cstdlib>
24+
#include <future>
25+
26+
#include <speex/speex_header.h>
27+
28+
namespace nativeformat {
29+
namespace decoder {
30+
31+
DecoderSpeexImplementation::DecoderSpeexImplementation(std::shared_ptr<DataProvider> &data_provider)
32+
: _data_provider(data_provider),
33+
_state(nullptr),
34+
_channels(1),
35+
_samplerate(0.0),
36+
_frames(0),
37+
_frame_index(0),
38+
_current_section(0) {}
39+
40+
DecoderSpeexImplementation::~DecoderSpeexImplementation() {
41+
if (_state != nullptr) {
42+
speex_decoder_destroy(_state);
43+
speex_bits_destroy(&_bits);
44+
_state = nullptr;
45+
}
46+
}
47+
48+
const std::string &DecoderSpeexImplementation::name() {
49+
static const std::string domain("com.nativeformat.decoder.speex");
50+
return domain;
51+
}
52+
53+
void DecoderSpeexImplementation::load(const ERROR_DECODER_CALLBACK &decoder_error_callback,
54+
const LOAD_DECODER_CALLBACK &decoder_load_callback) {
55+
std::shared_ptr<DecoderSpeexImplementation> strong_this = shared_from_this();
56+
_load_future = std::async(
57+
std::launch::async, [strong_this, decoder_error_callback, decoder_load_callback]() {
58+
{
59+
std::lock_guard<std::mutex> speex_lock(strong_this->_speex_mutex);
60+
strong_this->_state = speex_decoder_init(&speex_nb_mode);
61+
int tmp = 1;
62+
speex_decoder_ctl(strong_this->_state, SPEEX_SET_ENH, &tmp);
63+
speex_bits_init(&strong_this->_bits);
64+
SpeexHeader header;
65+
strong_this->_data_provider->read(&header, sizeof(SpeexHeader), 1);
66+
strong_this->_samplerate = header.rate;
67+
strong_this->_channels = header.nb_channels;
68+
}
69+
decoder_load_callback(true);
70+
});
71+
}
72+
73+
double DecoderSpeexImplementation::sampleRate() {
74+
return _samplerate;
75+
}
76+
77+
int DecoderSpeexImplementation::channels() {
78+
return _channels;
79+
}
80+
81+
long DecoderSpeexImplementation::currentFrameIndex() {
82+
return _frame_index;
83+
}
84+
85+
void DecoderSpeexImplementation::seek(long frame_index) {
86+
flush();
87+
{
88+
std::lock_guard<std::mutex> speex_lock(_speex_mutex);
89+
_data_provider->seek(sizeof(SpeexHeader), SEEK_SET);
90+
int frame_size = 0;
91+
speex_decoder_ctl(_state, SPEEX_GET_FRAME_SIZE, &frame_size);
92+
long current_frame_index = 0;
93+
while (!_data_provider->eof() && current_frame_index < frame_index) {
94+
_cached_samples.clear();
95+
char read_bytes[256];
96+
const auto bytes_read = _data_provider->read(read_bytes, sizeof(char), sizeof(read_bytes));
97+
speex_bits_read_from(&_bits, read_bytes, bytes_read);
98+
float samples[frame_size];
99+
const auto samples_read = speex_decode(_state, &_bits, samples);
100+
_cached_samples.insert(_cached_samples.begin(), samples, samples + samples_read);
101+
current_frame_index += samples_read;
102+
}
103+
_frame_index = frame_index;
104+
}
105+
}
106+
107+
long DecoderSpeexImplementation::frames() {
108+
return UNKNOWN_FRAMES;
109+
}
110+
111+
void DecoderSpeexImplementation::decode(long frames,
112+
const DECODE_CALLBACK &decode_callback,
113+
bool synchronous) {
114+
std::shared_ptr<DecoderSpeexImplementation> strong_this = shared_from_this();
115+
auto run_thread = [strong_this, decode_callback, frames] {
116+
long frame_index = strong_this->currentFrameIndex();
117+
float *samples = (float *)malloc(frames * sizeof(float) * strong_this->_channels);
118+
long read_frames = 0;
119+
{
120+
std::lock_guard<std::mutex> speex_lock(strong_this->_speex_mutex);
121+
int frame_size = 0;
122+
speex_decoder_ctl(strong_this->_state, SPEEX_GET_FRAME_SIZE, &frame_size);
123+
while (!strong_this->_data_provider->eof() && strong_this->_cached_samples.size() < frames) {
124+
char read_bytes[256];
125+
const auto bytes_read = strong_this->_data_provider->read(read_bytes, sizeof(char), sizeof(read_bytes));
126+
speex_bits_read_from(&strong_this->_bits, read_bytes, bytes_read);
127+
float samples[frame_size];
128+
const auto samples_read = speex_decode(strong_this->_state, &strong_this->_bits, samples);
129+
strong_this->_cached_samples.insert(strong_this->_cached_samples.end(), samples, samples + samples_read);
130+
}
131+
}
132+
read_frames = std::min(static_cast<long>(strong_this->_cached_samples.size()), frames);
133+
memcpy(samples, strong_this->_cached_samples.data(), read_frames * sizeof(float));
134+
strong_this->_cached_samples.erase(strong_this->_cached_samples.begin(), strong_this->_cached_samples.begin() + read_frames);
135+
decode_callback(frame_index, read_frames, samples);
136+
free(samples);
137+
};
138+
if (synchronous) {
139+
run_thread();
140+
} else {
141+
std::thread(run_thread).detach();
142+
}
143+
}
144+
145+
bool DecoderSpeexImplementation::eof() {
146+
return _data_provider->eof();
147+
}
148+
149+
const std::string &DecoderSpeexImplementation::path() {
150+
return _data_provider->path();
151+
}
152+
153+
void DecoderSpeexImplementation::flush() {
154+
std::lock_guard<std::mutex> speex_lock(_speex_mutex);
155+
speex_decoder_ctl(_state, SPEEX_RESET_STATE, nullptr);
156+
speex_bits_reset(&_bits);
157+
_cached_samples.clear();
158+
}
159+
160+
} // namespace decoder
161+
} // namespace nativeformat

0 commit comments

Comments
 (0)