From 897b2d489b427f09f3f86109b00abfa9029eb023 Mon Sep 17 00:00:00 2001 From: Lorenzo Miniero Date: Fri, 2 Dec 2022 16:28:33 +0100 Subject: [PATCH] Add support for L16 codec (raw samples) --- src/Makefile.am | 2 + src/postprocessing/janus-pp-rec.c | 30 ++++- src/postprocessing/pp-l16.c | 188 ++++++++++++++++++++++++++++++ src/postprocessing/pp-l16.h | 25 ++++ src/record.c | 2 +- src/rtp.c | 14 +++ src/rtp.h | 4 +- src/sdp-utils.c | 28 ++++- src/utils.c | 12 ++ 9 files changed, 301 insertions(+), 4 deletions(-) create mode 100644 src/postprocessing/pp-l16.c create mode 100644 src/postprocessing/pp-l16.h diff --git a/src/Makefile.am b/src/Makefile.am index 3c02a8ad86..96d361e53b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -558,6 +558,8 @@ janus_pp_rec_SOURCES = \ postprocessing/pp-g711.h \ postprocessing/pp-g722.c \ postprocessing/pp-g722.h \ + postprocessing/pp-l16.c \ + postprocessing/pp-l16.h \ postprocessing/pp-h264.c \ postprocessing/pp-h264.h \ postprocessing/pp-av1.c \ diff --git a/src/postprocessing/janus-pp-rec.c b/src/postprocessing/janus-pp-rec.c index 71a4f65d50..802ef686eb 100644 --- a/src/postprocessing/janus-pp-rec.c +++ b/src/postprocessing/janus-pp-rec.c @@ -129,6 +129,7 @@ Usage: janus-pp-rec [OPTIONS] source.mjr #include "pp-opus.h" #include "pp-g711.h" #include "pp-g722.h" +#include "pp-l16.h" #include "pp-srt.h" #include "pp-binary.h" @@ -237,6 +238,7 @@ int main(int argc, char *argv[]) { JANUS_LOG(LOG_INFO, " -- Opus: %s\n", janus_pp_extensions_string(janus_pp_opus_get_extensions(), supported, sizeof(supported))); JANUS_LOG(LOG_INFO, " -- G.711: %s\n", janus_pp_extensions_string(janus_pp_g711_get_extensions(), supported, sizeof(supported))); JANUS_LOG(LOG_INFO, " -- G.722: %s\n", janus_pp_extensions_string(janus_pp_g722_get_extensions(), supported, sizeof(supported))); + JANUS_LOG(LOG_INFO, " -- L16: %s\n", janus_pp_extensions_string(janus_pp_l16_get_extensions(), supported, sizeof(supported))); JANUS_LOG(LOG_INFO, " -- VP8: %s\n", janus_pp_extensions_string(janus_pp_webm_get_extensions(), supported, sizeof(supported))); JANUS_LOG(LOG_INFO, " -- VP9: %s\n", janus_pp_extensions_string(janus_pp_webm_get_extensions(), supported, sizeof(supported))); JANUS_LOG(LOG_INFO, " -- H.264: %s\n", janus_pp_extensions_string(janus_pp_h264_get_extensions(), supported, sizeof(supported))); @@ -374,7 +376,7 @@ int main(int argc, char *argv[]) { gboolean has_timestamps = FALSE; gboolean parsed_header = FALSE; gboolean video = FALSE, data = FALSE, textdata = FALSE; - gboolean opus = FALSE, multiopus = FALSE, g711 = FALSE, g722 = FALSE, + gboolean opus = FALSE, multiopus = FALSE, g711 = FALSE, g722 = FALSE, l16 = FALSE, l16_48k = FALSE, vp8 = FALSE, vp9 = FALSE, h264 = FALSE, av1 = FALSE, h265 = FALSE; int opusred_pt = 0; gboolean e2ee = FALSE; @@ -618,6 +620,16 @@ int main(int argc, char *argv[]) { janus_pprec_options_destroy(); exit(1); } + } else if(!strcasecmp(c, "l16") || !strcasecmp(c, "l16-48")) { + l16 = TRUE; + l16_48k = !strcasecmp(c, "l16-48"); + if(extension && !janus_pp_extension_check(extension, janus_pp_l16_get_extensions())) { + JANUS_LOG(LOG_ERR, "L16 RTP packets cannot be converted to this target file, at the moment (supported formats: %s)\n", + janus_pp_extensions_string(janus_pp_l16_get_extensions(), supported, sizeof(supported))); + json_decref(info); + janus_pprec_options_destroy(); + exit(1); + } } else { JANUS_LOG(LOG_WARN, "The post-processor only supports Opus, G.711 and G.722 audio for now (was '%s')...\n", c); json_decref(info); @@ -1143,6 +1155,8 @@ int main(int argc, char *argv[]) { int rate = video ? 90000 : 48000; if(g711 || g722) rate = 8000; + else if(l16 && !l16_48k) + rate = 16000; double ts = 0.0, pts = 0.0; while(tmp) { count++; @@ -1353,6 +1367,14 @@ int main(int argc, char *argv[]) { janus_pprec_options_destroy(); exit(1); } + } else if(l16) { + if(janus_pp_l16_create(destination, l16_48k ? 48000 : 16000, metadata) < 0) { + JANUS_LOG(LOG_ERR, "Error creating .wav file...\n"); + g_free(metadata); + g_free(extension); + janus_pprec_options_destroy(); + exit(1); + } } } else if(data) { if(textdata) { @@ -1422,6 +1444,10 @@ int main(int argc, char *argv[]) { if(janus_pp_g722_process(file, list, &working) < 0) { JANUS_LOG(LOG_ERR, "Error processing G.722 RTP frames...\n"); } + } else if(l16) { + if(janus_pp_l16_process(file, list, &working) < 0) { + JANUS_LOG(LOG_ERR, "Error processing L16 RTP frames...\n"); + } } } else if(data) { if(textdata) { @@ -1477,6 +1503,8 @@ int main(int argc, char *argv[]) { janus_pp_g711_close(); } else if(g722) { janus_pp_g722_close(); + } else if(l16) { + janus_pp_l16_close(); } } fclose(file); diff --git a/src/postprocessing/pp-l16.c b/src/postprocessing/pp-l16.c new file mode 100644 index 0000000000..8947607e03 --- /dev/null +++ b/src/postprocessing/pp-l16.c @@ -0,0 +1,188 @@ +/*! \file pp-l16.c + * \author Lorenzo Miniero + * \copyright GNU General Public License v3 + * \brief Post-processing to generate .wav files out of L16 frames (headers) + * \details Implementation of the post-processing code needed to + * generate raw .wav files out of L16 RTP frames. + * + * \ingroup postprocessing + * \ref postprocessing + */ + +#include +#if defined(__MACH__) || defined(__FreeBSD__) +#include +#else +#include +#endif +#include +#include +#include + +#include "pp-l16.h" +#include "../debug.h" + + +/* WAV header */ +typedef struct janus_pp_l16_wav { + char riff[4]; + uint32_t len; + char wave[4]; + char fmt[4]; + uint32_t formatsize; + uint16_t format; + uint16_t channels; + uint32_t samplerate; + uint32_t avgbyterate; + uint16_t samplebytes; + uint16_t channelbits; + char data[4]; + uint32_t blocksize; +} janus_pp_l16_wav; +static FILE *wav_file = NULL; + +/* Supported target formats */ +static const char *janus_pp_l16_formats[] = { + "wav", NULL +}; +const char **janus_pp_l16_get_extensions(void) { + return janus_pp_l16_formats; +} + +/* Processing methods */ +static int samplerate = 0; +int janus_pp_l16_create(char *destination, int rate, char *metadata) { + samplerate = rate; + if(samplerate != 16000 && samplerate != 48000) { + JANUS_LOG(LOG_ERR, "Unsupported sample rate %d (should be 16000 or 48000)\n", rate); + return -1; + } + /* Create wav file */ + wav_file = fopen(destination, "wb"); + if(wav_file == NULL) { + JANUS_LOG(LOG_ERR, "Couldn't open output file\n"); + return -1; + } + /* Add header */ + JANUS_LOG(LOG_INFO, "Writing .wav file header\n"); + janus_pp_l16_wav header = { + {'R', 'I', 'F', 'F'}, + 0, + {'W', 'A', 'V', 'E'}, + {'f', 'm', 't', ' '}, + 16, + 1, + 1, + samplerate, + samplerate * 2, + 2, + 16, + {'d', 'a', 't', 'a'}, + 0 + }; + /* Note: .wav files don't seem to support arbitrary comments + * so there's nothing we can do with the provided metadata*/ + if(fwrite(&header, 1, sizeof(header), wav_file) != sizeof(header)) { + JANUS_LOG(LOG_ERR, "Couldn't write WAV header, expect problems...\n"); + } + fflush(wav_file); + return 0; +} + +int janus_pp_l16_process(FILE *file, janus_pp_frame_packet *list, int *working) { + if(!file || !list || !working) + return -1; + janus_pp_frame_packet *tmp = list; + long int offset = 0; + int bytes = 0, len = 0, steps = 0, last_seq = 0; + uint8_t *buffer = g_malloc0(1500); + int16_t samples[1500]; + memset(samples, 0, sizeof(samples)); + size_t num_samples = samplerate/100/2; + int sr = samplerate/1000; + while(*working && tmp != NULL) { + if(tmp->prev != NULL && ((tmp->ts - tmp->prev->ts)/sr/10 > 1)) { + JANUS_LOG(LOG_WARN, "Lost a packet here? (got seq %"SCNu16" after %"SCNu16", time ~%"SCNu64"s)\n", + tmp->seq, tmp->prev->seq, (tmp->ts-list->ts)/samplerate); + int silence_count = (tmp->ts - tmp->prev->ts)/sr/10 - 1; + int i=0; + for(i=0; iprev->seq+i+1, i+1); + /* Add silence */ + memset(samples, 0, num_samples*2); + if(wav_file != NULL) { + if(fwrite(samples, sizeof(char), num_samples*2, wav_file) != num_samples) { + JANUS_LOG(LOG_ERR, "Couldn't write sample...\n"); + } + fflush(wav_file); + } + } + } + if(tmp->drop) { + /* We marked this packet as one to drop, before */ + JANUS_LOG(LOG_WARN, "Dropping previously marked audio packet (time ~%"SCNu64"s)\n", (tmp->ts-list->ts)/8000); + tmp = tmp->next; + continue; + } + if(tmp->audiolevel != -1) { + JANUS_LOG(LOG_VERB, "Audio level: %d dB\n", tmp->audiolevel); + } + guint16 diff = tmp->prev == NULL ? 1 : (tmp->seq - tmp->prev->seq); + len = 0; + /* RTP payload */ + offset = tmp->offset+12+tmp->skip; + fseek(file, offset, SEEK_SET); + len = tmp->len-12-tmp->skip; + if(len < 1) { + tmp = tmp->next; + continue; + } + bytes = fread(buffer, sizeof(char), len, file); + if(bytes != len) { + JANUS_LOG(LOG_WARN, "Didn't manage to read all the bytes we needed (%d < %d)...\n", bytes, len); + tmp = tmp->next; + continue; + } + if(last_seq == 0) + last_seq = tmp->seq; + if(tmp->seq < last_seq) { + last_seq = tmp->seq; + steps++; + } + JANUS_LOG(LOG_VERB, "Writing %d bytes out of %d (seq=%"SCNu16", step=%"SCNu16", ts=%"SCNu64", time=%"SCNu64"s)\n", + bytes, tmp->len, tmp->seq, diff, tmp->ts, (tmp->ts-list->ts)/samplerate); + num_samples = bytes/2; + int i=0; + for(i=0; i<(int)num_samples; i++) { + memcpy(&samples[i], buffer + i*2, sizeof(int16_t)); + samples[i] = ntohs(samples[i]); + } + if(wav_file != NULL) { + if(fwrite(samples, sizeof(int16_t), num_samples, wav_file) != num_samples) { + JANUS_LOG(LOG_ERR, "Couldn't write sample...\n"); + } + fflush(wav_file); + } + tmp = tmp->next; + } + g_free(buffer); + return 0; +} + +void janus_pp_l16_close(void) { + /* Flush and close file */ + if(wav_file != NULL) { + /* Update the header */ + fseek(wav_file, 0, SEEK_END); + uint32_t size = ftell(wav_file) - 8; + fseek(wav_file, 4, SEEK_SET); + fwrite(&size, sizeof(uint32_t), 1, wav_file); + size += 8; + fseek(wav_file, 40, SEEK_SET); + fwrite(&size, sizeof(uint32_t), 1, wav_file); + fflush(wav_file); + fclose(wav_file); + } + wav_file = NULL; +} diff --git a/src/postprocessing/pp-l16.h b/src/postprocessing/pp-l16.h new file mode 100644 index 0000000000..ca3310212c --- /dev/null +++ b/src/postprocessing/pp-l16.h @@ -0,0 +1,25 @@ +/*! \file pp-l16.h + * \author Lorenzo Miniero + * \copyright GNU General Public License v3 + * \brief Post-processing to generate .wav files out of L16 frames (headers) + * \details Implementation of the post-processing code needed to + * generate raw .wav files out of L16 RTP frames. + * + * \ingroup postprocessing + * \ref postprocessing + */ + +#ifndef JANUS_PP_L16 +#define JANUS_PP_L16 + +#include + +#include "pp-rtp.h" + +/* L16 stuff */ +const char **janus_pp_l16_get_extensions(void); +int janus_pp_l16_create(char *destination, int samplerate, char *metadata); +int janus_pp_l16_process(FILE *file, janus_pp_frame_packet *list, int *working); +void janus_pp_l16_close(void); + +#endif diff --git a/src/record.c b/src/record.c index 859a1cd69f..0a130ba2fd 100644 --- a/src/record.c +++ b/src/record.c @@ -97,7 +97,7 @@ janus_recorder *janus_recorder_create_full(const char *dir, const char *codec, c type = JANUS_RECORDER_VIDEO; } else if(!strcasecmp(codec, "opus") || !strcasecmp(codec, "multiopus") || !strcasecmp(codec, "g711") || !strcasecmp(codec, "pcmu") || !strcasecmp(codec, "pcma") - || !strcasecmp(codec, "g722")) { + || !strcasecmp(codec, "g722") || !strcasecmp(codec, "l16-48") || !strcasecmp(codec, "l16")) { type = JANUS_RECORDER_AUDIO; } else if(!strcasecmp(codec, "text") || !strcasecmp(codec, "binary")) { /* Data channels may be text or binary, so that's what we can save too */ diff --git a/src/rtp.c b/src/rtp.c index 833af19e9b..34dbf5ff26 100644 --- a/src/rtp.c +++ b/src/rtp.c @@ -873,6 +873,8 @@ const char *janus_srtp_error_str(int error) { #define PCMU_PT 0 #define PCMA_PT 8 #define G722_PT 9 +#define L16_48_PT 105 +#define L16_PT 106 #define VP8_PT 96 #define VP9_PT 101 #define H264_PT 107 @@ -898,6 +900,10 @@ const char *janus_audiocodec_name(janus_audiocodec acodec) { return "isac32"; case JANUS_AUDIOCODEC_ISAC_16K: return "isac16"; + case JANUS_AUDIOCODEC_L16_48K: + return "l16-48"; + case JANUS_AUDIOCODEC_L16_16K: + return "l16"; default: /* Shouldn't happen */ return "opus"; @@ -922,6 +928,10 @@ janus_audiocodec janus_audiocodec_from_name(const char *name) { return JANUS_AUDIOCODEC_PCMA; else if(!strcasecmp(name, "g722")) return JANUS_AUDIOCODEC_G722; + else if(!strcasecmp(name, "l16-48")) + return JANUS_AUDIOCODEC_L16_48K; + else if(!strcasecmp(name, "l16")) + return JANUS_AUDIOCODEC_L16_16K; JANUS_LOG(LOG_WARN, "Unsupported audio codec '%s'\n", name); return JANUS_AUDIOCODEC_NONE; } @@ -945,6 +955,10 @@ int janus_audiocodec_pt(janus_audiocodec acodec) { return PCMA_PT; case JANUS_AUDIOCODEC_G722: return G722_PT; + case JANUS_AUDIOCODEC_L16_48K: + return L16_48_PT; + case JANUS_AUDIOCODEC_L16_16K: + return L16_PT; default: /* Shouldn't happen */ return OPUS_PT; diff --git a/src/rtp.h b/src/rtp.h index b4689b6fdc..8b3134fd4c 100644 --- a/src/rtp.h +++ b/src/rtp.h @@ -105,7 +105,9 @@ typedef enum janus_audiocodec { JANUS_AUDIOCODEC_PCMA, JANUS_AUDIOCODEC_G722, JANUS_AUDIOCODEC_ISAC_32K, - JANUS_AUDIOCODEC_ISAC_16K + JANUS_AUDIOCODEC_ISAC_16K, + JANUS_AUDIOCODEC_L16_48K, + JANUS_AUDIOCODEC_L16_16K } janus_audiocodec; const char *janus_audiocodec_name(janus_audiocodec acodec); janus_audiocodec janus_audiocodec_from_name(const char *name); diff --git a/src/sdp-utils.c b/src/sdp-utils.c index 9a87437165..51a2d72d4a 100644 --- a/src/sdp-utils.c +++ b/src/sdp-utils.c @@ -21,7 +21,7 @@ /* Preferred codecs when negotiating audio/video, and number of supported codecs */ const char *janus_preferred_audio_codecs[] = { - "opus", "multiopus", "pcmu", "pcma", "g722", "isac16", "isac32" + "opus", "multiopus", "pcmu", "pcma", "g722", "l16-48", "l16", "isac16", "isac32" }; uint janus_audio_codecs = sizeof(janus_preferred_audio_codecs)/sizeof(*janus_preferred_audio_codecs); const char *janus_preferred_video_codecs[] = { @@ -745,6 +745,12 @@ int janus_sdp_get_codec_pt_full(janus_sdp *sdp, int index, const char *codec, co } else if(!strcasecmp(codec, "isac32")) { format = "isac/32000"; format2 = "ISAC/32000"; + } else if(!strcasecmp(codec, "l16-48")) { + format = "l16/48000"; + format2 = "L16/48000"; + } else if(!strcasecmp(codec, "l16")) { + format = "l16/16000"; + format2 = "L16/16000"; } else if(!strcasecmp(codec, "dtmf")) { format = "telephone-event/8000"; format2 = "TELEPHONE-EVENT/8000"; @@ -911,6 +917,10 @@ const char *janus_sdp_get_codec_name(janus_sdp *sdp, int index, int pt) { return "isac16"; if(strstr(a->value, "isac/32") || strstr(a->value, "ISAC/32")) return "isac32"; + if(strstr(a->value, "l16/48") || strstr(a->value, "L16/48")) + return "l16-48"; + if(strstr(a->value, "l16/16") || strstr(a->value, "L16/16")) + return "l16"; if(strstr(a->value, "telephone-event/8000") || strstr(a->value, "telephone-event/8000")) return "dtmf"; /* RED is not really a codec, but we need to detect it anyway */ @@ -948,6 +958,10 @@ const char *janus_sdp_get_rtpmap_codec(const char *rtpmap) { codec = "isac16"; else if(strstr(rtpmap_val, "isac/32") == rtpmap_val) codec = "isac32"; + else if(strstr(rtpmap_val, "l16/48") == rtpmap_val) + codec = "l16-48"; + else if(strstr(rtpmap_val, "l16/16") == rtpmap_val) + codec = "l16"; else if(strstr(rtpmap_val, "telephone-event/") == rtpmap_val) codec = "dtmf"; else if(strstr(rtpmap_val, "vp8/") == rtpmap_val) @@ -984,6 +998,10 @@ const char *janus_sdp_get_codec_rtpmap(const char *codec) { return "ISAC/16000"; if(!strcasecmp(codec, "isac32")) return "ISAC/32000"; + if(!strcasecmp(codec, "l16-48")) + return "L16/48000"; + if(!strcasecmp(codec, "l16")) + return "L16/16000"; if(!strcasecmp(codec, "dtmf")) return "telephone-event/8000"; if(!strcasecmp(codec, "vp8")) @@ -2040,6 +2058,14 @@ int janus_sdp_generate_answer_mline(janus_sdp *offer, janus_sdp *answer, janus_s if(janus_sdp_get_codec_pt(offer, offered->index, codec) < 0) { /* isac16 not found, maybe multiopus? */ codec = "multiopus"; + if(janus_sdp_get_codec_pt(offer, offered->index, codec) < 0) { + /* multiopus not found, maybe L16/48000? */ + codec = "l16-48"; + if(janus_sdp_get_codec_pt(offer, offered->index, codec) < 0) { + /* L16/48000 not found, maybe L16/16000? */ + codec = "l16"; + } + } } } } diff --git a/src/utils.c b/src/utils.c index 37c0874d8b..fdaef71c4e 100644 --- a/src/utils.c +++ b/src/utils.c @@ -369,6 +369,14 @@ int janus_get_codec_pt(const char *sdp, const char *codec) { video = 0; format = "isac/32000"; format2 = "ISAC/32000"; + } else if(!strcasecmp(codec, "l16-48")) { + video = 0; + format = "l16/48000"; + format2 = "L16/48000"; + } else if(!strcasecmp(codec, "l16")) { + video = 0; + format = "l16/16000"; + format2 = "L16/16000"; } else if(!strcasecmp(codec, "vp8")) { video = 1; format = "vp8/90000"; @@ -479,6 +487,10 @@ const char *janus_get_codec_from_pt(const char *sdp, int pt) { return "isac16"; if(strstr(name, "isac/32") || strstr(name, "ISAC/32")) return "isac32"; + if(strstr(name, "l16/48") || strstr(name, "L16/48")) + return "l16-48"; + if(strstr(name, "l16/16") || strstr(name, "L16/16")) + return "l16"; if(strstr(name, "red")) return NULL; JANUS_LOG(LOG_ERR, "Unsupported codec '%s'\n", name);