audd-c
Recognize music in audio clips, long broadcasts, and live streams from C.
include(FetchContent)
FetchContent_Declare(audd
GIT_REPOSITORY https://github.com/AudDMusic/audd-c.git
GIT_TAG v1.5.7
)
FetchContent_MakeAvailable(audd)
target_link_libraries(your_app PRIVATE audd)
Or, with the library installed system-wide:
cc myapp.c -laudd -lcurl -lpthread -o myapp
#include <audd.h>
#include <stdio.h>
int main(void)
{
/* 10 reqs/day; get a real token at https://dashboard.audd.io */
audd_client_t *client = audd_client_new("test", NULL);
audd_recognition_t *r = NULL;
audd_error_t err = audd_recognize(client, "https://audd.tech/example.mp3",
NULL, &r);
if (err == AUDD_OK && r != NULL) {
printf("%s — %s\n",
audd_recognition_get_artist(r),
audd_recognition_get_title(r));
}
audd_recognition_free(r);
audd_client_free(client);
return 0;
}
_new has a matching _freeC has no destructors. Every handle the SDK hands back — audd_client_t,
audd_recognition_t, audd_enterprise_result_t, audd_stream_list_t,
audd_stream_callback_match_t, audd_stream_callback_notification_t,
audd_lyrics_list_t, plus heap strings from audd_streams_get_callback_url
and audd_advanced_raw_request — must be released with the matching
_free call. All _free functions are NULL-safe.
Authentication
Get your API token at dashboard.audd.io.
audd_client_new(token, options) resolves the API token in this order:
- The
api_tokenargument, when non-NULL and non-empty. - The
AUDD_API_TOKENenvironment variable. - Otherwise the client is still constructed; the first network call
returns
AUDD_ERR_AUTHENTICATION.
audd_client_t *client = audd_client_new("your-api-token", NULL); /* explicit */
audd_client_t *client = audd_client_new(NULL, NULL); /* AUDD_API_TOKEN */
To fail fast at construction time, use audd_client_new_strict — it
writes AUDD_ERR_AUTHENTICATION into its out-param and returns NULL when
no token is configured:
audd_error_t err = AUDD_OK;
audd_client_t *client = audd_client_new_strict(NULL, NULL, &err);
if (client == NULL) {
fprintf(stderr, "audd: %s\n", audd_error_string(err));
return 1;
}
The public "test" token is capped at 10 requests/day on the standard
endpoint only — fine for hello-worlds, not enough for production.
For long-running services that pull tokens from a secret manager, rotate without rebuilding the client:
audd_client_set_api_token(client, "new-token");
The swap is atomic with respect to in-flight requests: in-flight calls
finish under the previous token; subsequent calls use the new value.
audd_client_api_token(client) returns a borrowed pointer to the
in-effect token (NULL when none set).
Recognize a clip
audd_recognize takes a 5–25-second clip — URL or local file path —
and disambiguates three outcomes via the return code plus the
out-param:
AUDD_OK+*out != NULL— match found.AUDD_OK+*out == NULL— clip processed, no match.- anything else — error; check
audd_last_error_message(client).
audd_recognition_t *r = NULL;
audd_error_t e = audd_recognize(client, "https://audd.tech/example.mp3", NULL, &r);
/* or: audd_recognize(client, "/path/to/clip.mp3", NULL, &r); */
if (e == AUDD_OK && r != NULL) {
printf("%s — %s @ %s\n",
audd_recognition_get_artist(r),
audd_recognition_get_title(r),
audd_recognition_get_timecode(r));
printf("song page: %s\n", audd_recognition_get_song_link(r));
const char *cover = audd_recognition_thumbnail_url(r);
if (cover) printf("cover art: %s\n", cover);
}
audd_recognition_free(r); /* NULL-safe */
For in-memory data, use audd_recognize_bytes(client, buf, len, opts, &r)
— the buffer is copied, so it's safe to free after the call.
A recognition exposes artist, title, album, release_date,
label, timecode, song_link, isrc / upc (enterprise plans),
plus audd_recognition_thumbnail_url,
audd_recognition_streaming_url(r, provider),
audd_recognition_preview_url, and audd_recognition_extra(r, "key")
for fields not yet wrapped. Strings are owned by the recognition
handle — strdup them if they need to outlive
audd_recognition_free. Full reference: github.com/AudDMusic/audd-c#what-you-get-back.
Process a long audio file
For broadcasts, podcasts, and full sets longer than 25 seconds, use
audd_recognize_enterprise. AudD chunks the file server-side and
returns every match.
audd_enterprise_options_t opts = audd_enterprise_options_default();
opts.limit = 10; /* ALWAYS set this in development */
audd_enterprise_result_t *result = NULL;
audd_error_t e = audd_recognize_enterprise(client, "/path/to/broadcast.mp3",
&opts, &result);
if (e == AUDD_OK) {
for (size_t i = 0; i < audd_enterprise_result_count(result); ++i) {
const audd_enterprise_match_t *m = audd_enterprise_result_at(result, i);
printf("%s %s — %s (score=%d)\n",
audd_enterprise_match_get_timecode(m),
audd_enterprise_match_get_artist(m),
audd_enterprise_match_get_title(m),
audd_enterprise_match_get_score(m));
}
}
audd_enterprise_result_free(result);
audd_enterprise_options_default() sets every numeric field to -1
("not set"). Other knobs: skip, every, skip_first_seconds,
use_timecode, accurate_offsets. audd_recognize_enterprise_bytes
accepts an in-memory buffer.
Always set opts.limit during development. The unbounded default can
ingest many hours of audio on a single call.
Each match exposes score, timecode, artist, title, album,
release_date, label, song_link, isrc / upc (enterprise
plans), start_offset, end_offset, _thumbnail_url,
_streaming_url(provider). Match pointers are borrowed — do not free
them individually; free the parent result.
Get streaming-service metadata
Pass return_metadata to populate provider sub-objects on the result.
Without it, those blocks are NULL.
const char *want[] = { "apple_music", "spotify", NULL };
audd_recognize_options_t opts = { .return_metadata = want };
audd_recognition_t *r = NULL;
audd_error_t e = audd_recognize(client, "https://audd.tech/example.mp3", &opts, &r);
if (e == AUDD_OK && r != NULL) {
const audd_apple_music_t *am = audd_recognition_apple_music(r);
if (am) printf("Apple Music: %s\n", audd_apple_music_get_url(am));
const audd_spotify_t *sp = audd_recognition_spotify(r);
if (sp) printf("Spotify URI: %s\n", audd_spotify_get_uri(sp));
/* Or resolve any provider — direct URL when the metadata block is set,
else the lis.tn redirect when song_link is on lis.tn. */
printf("%s\n", audd_recognition_streaming_url(r, AUDD_PROVIDER_SPOTIFY));
printf("preview: %s\n", audd_recognition_preview_url(r));
audd_recognition_free(r);
}
Valid providers (the strings in return_metadata): apple_music,
spotify, deezer, napster, musicbrainz. Each adds latency.
The audd_provider_t enum (used by audd_recognition_streaming_url)
covers SPOTIFY, APPLE_MUSIC, DEEZER, NAPSTER, YOUTUBE.
The market option (ISO 3166 region code) controls the Apple Music
storefront:
audd_recognize_options_t opts = { .return_metadata = want, .market = "GB" };
For each provider sub-object's full field shape, see the upstream API reference and the GitHub README.
Monitor a live audio stream
A stream in AudD is a long-running audio source — typically a radio
URL, HLS, Icecast, or SHOUTcast endpoint — that AudD ingests
continuously and recognizes against. Each stream is identified by a
radio_id you pick.
There are two ways to consume recognition events:
- Callbacks — AudD POSTs each match (and each lifecycle notification) to a URL you control. See Receive callbacks from a stream.
- Longpoll — your code polls AudD's
/longpoll/endpoint synchronously over HTTP. See Poll for stream events (longpoll).
Both modes need a callback URL configured on the account; otherwise
events are never delivered. If your consumers will only longpoll — e.g., browser widgets, mobile apps, or scripts that can't expose a public URL — set the account's callback URL to https://audd.tech/empty/ in advance; it's a placeholder URL that discards POSTs. Consumers themselves don't need to configure anything.
Receive callbacks from a stream
Setup is three steps: configure the callback URL, register the stream, and parse incoming POSTs.
1. Configure the account callback URL
const char *want[] = { "apple_music", "spotify", NULL };
audd_streams_set_callback_url_options_t copts = { .return_metadata = want };
audd_streams_set_callback_url(client,
"https://your-app.example.com/audd-callback", &copts);
return_metadata is serialized into the URL as ?return=apple_music,spotify
(the server reads it from the URL string).
2. Register the stream
int radio_id = 1; /* any integer you choose — your handle for this stream */
audd_add_stream_request_t req = {
.url = "https://radio.example.com/stream.mp3",
.radio_id = radio_id,
.callbacks = NULL, /* "before" → callback at song start */
};
audd_streams_add(client, &req);
Other stream-management calls:
int radio_id = 1; /* your stream's handle from when you called audd_streams_add */
audd_streams_set_url(client, radio_id, "https://radio.example.com/new.mp3");
audd_streams_delete(client, radio_id);
audd_stream_list_t *list = NULL;
audd_streams_list(client, &list);
for (size_t i = 0; i < audd_stream_list_count(list); ++i) {
const audd_stream_t *s = audd_stream_list_at(list, i);
printf("%d %s running=%d\n",
audd_stream_get_radio_id(s),
audd_stream_get_url(s),
audd_stream_get_running(s));
}
audd_stream_list_free(list);
3. Parse incoming POST bodies
audd_parse_callback(body, size, &match, ¬if, &err) is transport-
agnostic and pure: feed it raw bytes from any HTTP framework. Exactly
one of match / notif is non-NULL on success.
libmicrohttpd — canonical recipe
#include <audd.h>
#include <microhttpd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct ctx { char *body; size_t len; };
static void dispatch(const char *body, size_t len)
{
audd_stream_callback_match_t *m = NULL;
audd_stream_callback_notification_t *n = NULL;
char *err = NULL;
if (audd_parse_callback(body, len, &m, &n, &err) != AUDD_OK) {
audd_string_free(err); return;
}
if (m) {
const audd_stream_callback_song_t *s = audd_stream_callback_match_get_song(m);
printf("matched on radio_id=%lld: %s — %s\n",
(long long)audd_stream_callback_match_get_radio_id(m),
audd_stream_callback_song_get_artist(s),
audd_stream_callback_song_get_title(s));
audd_stream_callback_match_free(m);
} else if (n) {
printf("notification #%d: %s\n",
audd_stream_callback_notification_get_code(n),
audd_stream_callback_notification_get_message(n));
audd_stream_callback_notification_free(n);
}
}
static enum MHD_Result handler(void *cls, struct MHD_Connection *conn,
const char *url, const char *method, const char *version,
const char *upload_data, size_t *upload_data_size, void **req_cls)
{
struct ctx *c = *req_cls;
if (c == NULL) { *req_cls = calloc(1, sizeof(*c)); return MHD_YES; }
if (*upload_data_size != 0) { /* buffer */
c->body = realloc(c->body, c->len + *upload_data_size);
memcpy(c->body + c->len, upload_data, *upload_data_size);
c->len += *upload_data_size; *upload_data_size = 0;
return MHD_YES;
}
dispatch(c->body, c->len); /* complete */
struct MHD_Response *r = MHD_create_response_from_buffer(
2, "ok", MHD_RESPMEM_PERSISTENT);
enum MHD_Result rv = MHD_queue_response(conn, MHD_HTTP_OK, r);
MHD_destroy_response(r);
free(c->body); free(c); *req_cls = NULL;
return rv;
}
int main(void)
{
struct MHD_Daemon *d = MHD_start_daemon(MHD_USE_INTERNAL_POLLING_THREAD,
8080, NULL, NULL, &handler, NULL, MHD_OPTION_END);
getchar(); MHD_stop_daemon(d);
}
A audd_stream_callback_match_t carries radio_id, timestamp,
play_length, the top song, alternative candidates (rare; may
have different artist/title from the top match), and the full
unparsed body. A audd_stream_callback_song_t mirrors a recognition's
metadata. A audd_stream_callback_notification_t carries radio_id,
stream_running (-1/0/1), code, message, and time.
Other HTTP servers (mongoose, civetweb, your own) — see the GitHub README.
Poll for stream events (longpoll)
For consumers that can't expose a public callback URL — embedded
devices, scripts behind NAT, native clients — poll /longpoll/
instead. The C consumer is built around three callback functions plus
a blocking audd_longpoll_run_by_radio_id. Pick any integer radio_id
as your handle for the stream.
Unless a callback URL is configured for the account, longpoll requests
just return keepalives — no events ever fire. The SDK preflights this
on your first run and surfaces AUDD_ERR_INVALID_REQUEST with
guidance if it's missing; set opts.skip_callback_check = 1 once
you've configured one.
If you only want longpoll and have no real receiver, set
https://audd.tech/empty/ — it's a placeholder URL that discards
incoming POSTs:
audd_streams_set_callback_url(client, "https://audd.tech/empty/", NULL);
static audd_longpoll_t *g_handle;
static void on_match(const audd_stream_callback_match_t *m, void *ud)
{
const audd_stream_callback_song_t *s = audd_stream_callback_match_get_song(m);
printf("matched: %s — %s\n",
audd_stream_callback_song_get_artist(s),
audd_stream_callback_song_get_title(s));
}
static void on_notification(const audd_stream_callback_notification_t *n, void *ud)
{
printf("notification: %s\n",
audd_stream_callback_notification_get_message(n));
}
static void on_error(audd_error_t err, const char *msg, void *ud)
{
fprintf(stderr, "terminal: %s — %s\n", audd_error_string(err), msg);
}
static void on_sigint(int sig) { audd_longpoll_close(g_handle); }
int main(void)
{
audd_client_t *client = audd_client_new(NULL, NULL); /* reads AUDD_API_TOKEN */
if (!client) return 1;
int radio_id = 1; /* any integer you choose — your handle for this stream */
audd_longpoll_callbacks_t cb = {
.on_match = on_match,
.on_notification = on_notification,
.on_error = on_error,
.user_data = NULL,
};
signal(SIGINT, on_sigint);
audd_error_t rc = audd_longpoll_run_by_radio_id(
client, radio_id, NULL, &cb, &g_handle);
if (rc != AUDD_OK) {
fprintf(stderr, "longpoll failed: %s\n", audd_error_string(rc));
}
/* Blocks until on_error fires or audd_longpoll_close is called.
The handle is freed automatically on return — don't use it after. */
audd_client_free(client);
return 0;
}
audd_longpoll_run_by_radio_id blocks the calling thread.
audd_longpoll_close is idempotent and safe to call from any thread
(including a signal handler, as above) — it's the only way to stop a
longpoll without a terminal error.
The handle written to out_handle is owned by the running call: it's
populated before the first poll request so external code can stash it
for audd_longpoll_close, and it becomes invalid the moment
audd_longpoll_run_by_radio_id returns. Don't call audd_longpoll_close
on it after that point.
audd_longpoll_options_t (passed in place of NULL above) exposes
since_time (Unix timestamp to resume from; 0 = "from now"),
timeout (server-side wait; default 50 seconds), and
skip_callback_check (set to 1 to bypass the preflight described
above). audd_longpoll_options_default() returns a zero-initialized
struct you can mutate.
Tokenless consumers
When the polling client must not hold the api_token — a browser widget, a mobile app, a shipped embedded device — keep the token on a trusted server and ship an opaque per-stream identifier to the client instead.
On the server, call audd_streams_derive_longpoll_category(client,
radio_id, out_category) (out_category must point to at least 10
bytes). It runs without any network I/O and writes a short opaque
string. Send only that string to the consumer.
char category[10];
audd_error_t rc = audd_streams_derive_longpoll_category(client, radio_id, category);
if (rc != AUDD_OK) { /* handle */ }
/* ship `category` to the tokenless consumer over your own channel */
On the consumer, build a tokenless client and pass the received
string to audd_longpoll_run:
audd_client_t *consumer = audd_client_new(NULL, NULL); /* no api_token needed */
audd_longpoll_run(consumer, category, NULL, &cb, &g_handle);
audd_derive_longpoll_category(api_token, radio_id, out_category) is
the same function without the client wrapper, for code paths that
hold the token directly.
Add a song to your custom catalog
The custom-catalog endpoint adds songs to a private fingerprint
database for your account. After upload, future audd_recognize
calls on the same account can match against your tracks. It is not
how you submit audio for music recognition — for that, use
audd_recognize (or audd_recognize_enterprise).
The custom-catalog endpoint requires special access. Contact
api@audd.io to enable it. Calls without access
return AUDD_ERR_CUSTOM_CATALOG_ACCESS.
int audio_id = 1; /* any integer you choose — your reference to the song */
audd_custom_catalog_add(client, audio_id, "https://my.cdn/song.mp3");
/* Or from in-memory bytes: */
audd_custom_catalog_add_bytes(client, audio_id, buf, buf_len);
The SDK exposes only add — there is no public list or delete.
Track audio_id ↔ song mappings yourself. Re-using an audio_id
re-fingerprints that slot.
Handle errors
C has no exceptions. Every fallible call returns an audd_error_t
sentinel that classifies the failure into a category (AUDD_ERR_QUOTA,
AUDD_ERR_INVALID_AUDIO, AUDD_ERR_CONNECTION, …). For the human-
readable detail and the AudD numeric error code, query the client.
Idiomatic error handling
audd_recognition_t *r = NULL;
audd_error_t e = audd_recognize(client, "/path/to/clip.mp3", NULL, &r);
switch (e) {
case AUDD_OK:
/* r is NULL on no-match, non-NULL on match */
break;
case AUDD_ERR_AUTHENTICATION:
fprintf(stderr, "check your token: [#%d] %s\n",
audd_last_error_code(client),
audd_last_error_message(client));
return 1;
case AUDD_ERR_QUOTA:
case AUDD_ERR_RATE_LIMIT:
fprintf(stderr, "throttled: %s\n", audd_last_error_message(client));
break;
case AUDD_ERR_INVALID_AUDIO:
fprintf(stderr, "audio rejected: %s\n", audd_last_error_message(client));
break;
case AUDD_ERR_CONNECTION:
fprintf(stderr, "network: %s\n", audd_last_error_message(client));
break;
default:
fprintf(stderr, "AudD #%d: %s (request_id=%s)\n",
audd_last_error_code(client),
audd_last_error_message(client),
audd_last_request_id(client));
}
audd_last_error_message, audd_last_error_code, and
audd_last_request_id always return non-NULL strings (empty when
there's nothing to report). They reflect the most recent failed call
on client; check them right after a non-AUDD_OK return, before
issuing another call.
audd_error_string(e) returns a stable English label for the sentinel
itself (e.g. "authentication failed", "invalid audio") without
querying a client — useful in error paths that don't have one yet.
For some codes — 19 (request_blocked, abuse, test_request_scope),
31337 (ip_ban), 903 (token_disabled) — the server populates
branded human-readable text alongside the error. The SDK surfaces it
via audd_last_error_message rather than exposing it as a fake
recognition match.
Retry behavior
Recognition calls (audd_recognize, audd_recognize_enterprise) retry
only on pre-upload network errors — DNS, connect, TLS — to avoid
double-billing for in-progress uploads. Read-class calls
(audd_streams_list, audd_streams_get_callback_url) retry on any
network error plus 5xx and 408/429. Mutating calls (audd_streams_add,
audd_streams_delete, audd_streams_set_*, audd_custom_catalog_*)
retry only on pre-upload errors; 5xx is surfaced because the side
effect may have already happened.
Defaults: max_attempts=3, backoff_ms=500 (geometric, doubles each
attempt). Override via audd_options_t:
audd_options_t opts = audd_options_default();
opts.max_attempts = 5;
opts.backoff_ms = 1000;
audd_client_t *client = audd_client_new("token", &opts);
Set max_attempts = 1 to disable retries.
Configuration
Timeouts
audd_options_t carries two timeouts in seconds — a short one for the
standard endpoint (and stream/catalog/advanced calls), a long one for
enterprise:
audd_options_t opts = audd_options_default();
opts.standard_timeout_seconds = 30; /* default 60 */
opts.enterprise_timeout_seconds = 7200; /* default 3600 (1h) — bump for very long files */
audd_client_t *client = audd_client_new("token", &opts);
TLS and User-Agent
audd_options_t opts = audd_options_default();
opts.ca_bundle_path = "/etc/ssl/internal-ca.pem"; /* corp roots */
opts.user_agent_suffix = "myapp/2.1"; /* appended to "audd-c/<ver>" */
Both pointers are stored by reference (not copied) — keep them alive for the client's lifetime.
Custom allocator
For arena, tracking, or pool allocators, plug in your own once at
process start, before any other audd_* call:
audd_allocator_t a = { my_malloc, my_free, my_realloc };
audd_set_allocator(&a);
Pass NULL to restore libc defaults. audd_set_allocator is
not thread-safe.
Concurrency and lifecycle
A single audd_client_t * is safe to share across threads. A
running audd_recognize can be cancelled from another thread by
audd_client_free (terminal); a running longpoll, by
audd_longpoll_close. audd_set_allocator is the one global mutator
that is NOT thread-safe — call it once at process start.
Token rotation
audd_client_set_api_token(client, "new-token") — see
Authentication.
Calling undocumented endpoints
For AudD endpoints not yet wrapped by typed functions on this SDK, hit
them by name through audd_advanced_raw_request. It returns the raw
JSON body as a heap string (free with audd_string_free), runs through
the same retry policy, and surfaces transport / parsing failures
through the usual sentinel + audd_last_error_message channel.
const char *params[] = { "audd_id", "12345", NULL };
char *body = NULL;
audd_advanced_raw_request(client, "getLinks", params, &body);
/* body is the raw JSON response. Parse with whatever you like. */
audd_string_free(body);
Typed wrappers ship as features stabilize; until then, this is the supported path for anything beta or one-off.