Skip to content

Commit eadfa2a

Browse files
Copilotarkq
andcommitted
Add Opus encoding support
Co-authored-by: arkq <6979798+arkq@users.noreply.github.com>
1 parent 4b2f659 commit eadfa2a

11 files changed

Lines changed: 210 additions & 39 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,4 @@ install_manifest.txt
5454
compile_commands.json
5555
CTestTestfile.cmake
5656
_deps
57+
build/

CMakeLists.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ endif()
2828
option(ENABLE_MP3LAME "Enable MP3 support.")
2929
option(ENABLE_SNDFILE "Enable WAV support.")
3030
option(ENABLE_VORBIS "Enable OGG support.")
31+
option(ENABLE_OPUS "Enable OPUS support.")
3132

3233
configure_file(
3334
${PROJECT_SOURCE_DIR}/config.h.in
@@ -83,6 +84,12 @@ if(ENABLE_VORBIS)
8384
target_link_libraries(svarcore PkgConfig::VorbisOgg)
8485
endif()
8586

87+
if(ENABLE_OPUS)
88+
pkg_check_modules(Opus REQUIRED IMPORTED_TARGET libopusenc ogg)
89+
target_sources(svarcore PRIVATE src/writer-opus.c)
90+
target_link_libraries(svarcore PkgConfig::Opus)
91+
endif()
92+
8693
if(BUILD_TESTING)
8794

8895
file(GLOB_RECURSE TEST_SOURCES test/tc-*.c)

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Supported output formats:
1919
- WAV, RF64 ([libsndfile](http://www.mega-nerd.com/libsndfile/))
2020
- MP3 ([mp3lame](http://lame.sourceforge.net/))
2121
- OGG ([libvorbis](http://www.xiph.org/vorbis/))
22+
- OPUS ([libopusenc](https://opus-codec.org/))
2223

2324
For low CPU consumption WAV is recommended - it is the default selection.
2425

@@ -39,6 +40,6 @@ from the last 100 ms of captured audio.
3940
mkdir build && cd build
4041
cmake .. \
4142
-DENABLE_ALSA=ON -DENABLE_PIPEWIRE=ON -DENABLE_PORTAUDIO=ON \
42-
-DENABLE_SNDFILE=ON -DENABLE_MP3LAME=ON -DENABLE_VORBIS=ON
43+
-DENABLE_SNDFILE=ON -DENABLE_MP3LAME=ON -DENABLE_VORBIS=ON -DENABLE_OPUS=ON
4344
make && make install
4445
```

build/config.h

Lines changed: 0 additions & 38 deletions
This file was deleted.

build/svar

-46.8 KB
Binary file not shown.

config.h.in

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@
2828
/* Define to 1 if Ogg Vorbis is enabled. */
2929
#cmakedefine ENABLE_VORBIS 1
3030

31+
/* Define to 1 if Ogg Opus is enabled. */
32+
#cmakedefine ENABLE_OPUS 1
33+
3134
/* Enable the use of GNU extensions. */
3235
#define _GNU_SOURCE
3336

src/main.c

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@
4242
#if ENABLE_VORBIS
4343
# include "writer-ogg.h"
4444
#endif
45+
#if ENABLE_OPUS
46+
# include "writer-opus.h"
47+
#endif
4548

4649
/* Application banner used for output file comment string. */
4750
static const char * banner = "SVAR - Simple Voice Activated Recorder";
@@ -101,6 +104,11 @@ static void print_audio_info(void) {
101104
if (writer->type == WRITER_TYPE_OGG)
102105
printf("Output bit rate [kbit/s]: min=%d nominal=%d max=%d\n",
103106
bitrate_min / 1000, bitrate_nom / 1000, bitrate_max / 1000);
107+
#endif
108+
#if ENABLE_OPUS
109+
if (writer->type == WRITER_TYPE_OPUS)
110+
printf("Output bit rate [kbit/s]: max=%d\n",
111+
bitrate_max / 1000);
104112
#endif
105113
}
106114
}
@@ -142,6 +150,8 @@ int main(int argc, char *argv[]) {
142150
/* Select default output format based on available libraries. */
143151
#if ENABLE_SNDFILE
144152
enum writer_type writer_type = WRITER_TYPE_WAV;
153+
#elif ENABLE_OPUS
154+
enum writer_type writer_type = WRITER_TYPE_OPUS;
145155
#elif ENABLE_VORBIS
146156
enum writer_type writer_type = WRITER_TYPE_OGG;
147157
#elif ENABLE_MP3LAME
@@ -252,6 +262,9 @@ int main(int argc, char *argv[]) {
252262
#endif
253263
#if ENABLE_VORBIS
254264
WRITER_TYPE_OGG,
265+
#endif
266+
#if ENABLE_OPUS
267+
WRITER_TYPE_OPUS,
255268
#endif
256269
};
257270

@@ -384,6 +397,12 @@ int main(int argc, char *argv[]) {
384397
writer = writer_ogg_new(pcm_format, pcm_channels, pcm_rate,
385398
bitrate_min, bitrate_nom, bitrate_max, banner);
386399
break;
400+
#endif
401+
#if ENABLE_OPUS
402+
case WRITER_TYPE_OPUS:
403+
writer = writer_opus_new(pcm_format, pcm_channels, pcm_rate,
404+
bitrate_min, bitrate_max, banner);
405+
break;
387406
#endif
388407
}
389408

src/writer-opus.c

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
/*
2+
* SVAR - writer-opus.c
3+
* SPDX-FileCopyrightText: 2010-2025 Arkadiusz Bokowy and contributors
4+
* SPDX-License-Identifier: MIT
5+
*/
6+
7+
#include "writer-opus.h"
8+
9+
#include <errno.h>
10+
#include <stdbool.h>
11+
#include <stdint.h>
12+
#include <stdio.h>
13+
#include <stdlib.h>
14+
#include <string.h>
15+
16+
#include <opusenc.h>
17+
18+
#include "log.h"
19+
#include "pcm.h"
20+
#include "writer.h"
21+
22+
struct writer_opus {
23+
OggOpusEnc * enc;
24+
OggOpusComments * comments;
25+
char * pathname;
26+
int bitrate_min;
27+
int bitrate_max;
28+
unsigned int channels;
29+
unsigned int sampling;
30+
};
31+
32+
static int writer_opus_open(struct writer * writer, const char * pathname) {
33+
struct writer_opus * w = writer->w;
34+
35+
writer->close(writer);
36+
37+
/* Store the pathname for later use when creating the encoder */
38+
free(w->pathname);
39+
if ((w->pathname = strdup(pathname)) == NULL)
40+
return -1;
41+
42+
int error;
43+
/* Create encoder with file path, comments, sample rate, channels, and family
44+
* Family 0 is used for mono/stereo (up to 2 channels) */
45+
int family = w->channels <= 2 ? 0 : 1;
46+
w->enc = ope_encoder_create_file(w->pathname, w->comments, w->sampling, w->channels, family, &error);
47+
48+
if (w->enc == NULL) {
49+
error("Couldn't create Opus encoder: %s", ope_strerror(error));
50+
errno = EINVAL;
51+
return -1;
52+
}
53+
54+
/* Set bitrate constraints using encoder control interface */
55+
if (w->bitrate_max > 0) {
56+
if (ope_encoder_ctl(w->enc, OPUS_SET_BITRATE(w->bitrate_max)) != OPE_OK)
57+
warn("Couldn't set Opus max bitrate");
58+
}
59+
60+
writer->opened = true;
61+
return 0;
62+
}
63+
64+
static void writer_opus_close(struct writer * writer) {
65+
struct writer_opus * w = writer->w;
66+
writer->opened = false;
67+
68+
if (w->enc == NULL)
69+
return;
70+
71+
/* Finalize the stream */
72+
ope_encoder_drain(w->enc);
73+
ope_encoder_destroy(w->enc);
74+
w->enc = NULL;
75+
}
76+
77+
static ssize_t writer_opus_write(struct writer * writer, const void * buffer, size_t frames) {
78+
struct writer_opus * w = writer->w;
79+
80+
/* Write 16-bit PCM samples to the encoder */
81+
int ret = ope_encoder_write(w->enc, (const opus_int16 *)buffer, frames);
82+
83+
if (ret != OPE_OK) {
84+
error("Opus encoding error: %s", ope_strerror(ret));
85+
return -1;
86+
}
87+
88+
/* Return the number of frames written (libopusenc doesn't return byte count) */
89+
return frames;
90+
}
91+
92+
static void writer_opus_free(struct writer * writer) {
93+
if (writer == NULL)
94+
return;
95+
struct writer_opus * w = writer->w;
96+
writer->close(writer);
97+
if (w->comments != NULL)
98+
ope_comments_destroy(w->comments);
99+
free(w->pathname);
100+
free(writer->w);
101+
free(writer);
102+
}
103+
104+
struct writer * writer_opus_new(
105+
enum pcm_format format, unsigned int channels, unsigned int sampling,
106+
int bitrate_min, int bitrate_max, const char * comment) {
107+
108+
if (format != PCM_FORMAT_S16LE) {
109+
error("Opus unsupported PCM format: %s", pcm_format_name(format));
110+
return NULL;
111+
}
112+
113+
struct writer * writer;
114+
if ((writer = malloc(sizeof(*writer))) == NULL)
115+
return NULL;
116+
117+
writer->type = WRITER_TYPE_OPUS;
118+
writer->opened = false;
119+
writer->open = writer_opus_open;
120+
writer->write = writer_opus_write;
121+
writer->close = writer_opus_close;
122+
writer->free = writer_opus_free;
123+
124+
struct writer_opus * w;
125+
if ((writer->w = w = calloc(1, sizeof(*w))) == NULL) {
126+
free(writer);
127+
return NULL;
128+
}
129+
130+
w->bitrate_min = bitrate_min;
131+
w->bitrate_max = bitrate_max;
132+
w->channels = channels;
133+
w->sampling = sampling;
134+
135+
/* Create comments object */
136+
w->comments = ope_comments_create();
137+
if (w->comments == NULL) {
138+
writer_opus_free(writer);
139+
return NULL;
140+
}
141+
142+
if (comment != NULL) {
143+
char tag[8 + strlen(comment) + 1];
144+
snprintf(tag, sizeof(tag), "ENCODER=%s", comment);
145+
ope_comments_add_string(w->comments, tag);
146+
}
147+
148+
return writer;
149+
}

src/writer-opus.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/*
2+
* SVAR - writer-opus.h
3+
* SPDX-FileCopyrightText: 2010-2025 Arkadiusz Bokowy and contributors
4+
* SPDX-License-Identifier: MIT
5+
*/
6+
7+
#pragma once
8+
#ifndef SVAR_WRITER_OPUS_H_
9+
#define SVAR_WRITER_OPUS_H_
10+
11+
#include "pcm.h"
12+
#include "writer.h"
13+
14+
struct writer * writer_opus_new(
15+
enum pcm_format format, unsigned int channels, unsigned int sampling,
16+
int bitrate_min, int bitrate_max, const char * comment);
17+
18+
#endif

src/writer.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,10 @@ const char * writer_type_to_extension(enum writer_type type) {
8989
#if ENABLE_VORBIS
9090
case WRITER_TYPE_OGG:
9191
return "ogg";
92+
#endif
93+
#if ENABLE_OPUS
94+
case WRITER_TYPE_OPUS:
95+
return "opus";
9296
#endif
9397
}
9498
return "unknown";
@@ -111,6 +115,10 @@ const char * writer_type_to_string(enum writer_type type) {
111115
#if ENABLE_VORBIS
112116
case WRITER_TYPE_OGG:
113117
return "ogg";
118+
#endif
119+
#if ENABLE_OPUS
120+
case WRITER_TYPE_OPUS:
121+
return "opus";
114122
#endif
115123
}
116124
return "unknown";

0 commit comments

Comments
 (0)