From 97d021424e89353677964a4aab71cbf46368ac4a Mon Sep 17 00:00:00 2001 From: Alliswell Date: Mon, 4 Oct 2021 02:16:43 +0800 Subject: [PATCH] feat: add G.711 a-law algorithm (#858) * feat: add G.711 a-law algorithm * chore: add CMakeLists.txt for audio/ * updating DIRECTORY.md * docs: add explanation to G.711 a-law algorithm * docs: adjust comments to G.711 a-law algorithm Co-authored-by: David Leal * docs: adjust comments to G.711 a-law algorithm Co-authored-by: David Leal * test: add self-test for G.711 a-law algorithm * fix: initialize variables to zero * docs: adjust comments to G.711 a-law algorithm Co-authored-by: David Leal Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: David Leal --- CMakeLists.txt | 5 +- DIRECTORY.md | 3 + audio/CMakeLists.txt | 14 +++ audio/alaw.c | 216 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 236 insertions(+), 2 deletions(-) create mode 100644 audio/CMakeLists.txt create mode 100644 audio/alaw.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 4a680b18..dafc51c7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,7 +18,7 @@ endif(MSVC) # addresses a bug when linking on OSX find_library(MATH_LIBRARY m) -# Optional flag - can be set by user +# Optional flag - can be set by user # Default "ON" option(USE_OPENMP "flag to use OpenMP for multithreading" ON) if(USE_OPENMP) @@ -46,12 +46,13 @@ if (NOT HAS_INTTYPES_H) message(FATAL_ERROR "Missing required header: 'inttypes.h'") endif() -## Add subdirectories containing CMakeLists.txt +## Add subdirectories containing CMakeLists.txt # to configure and compile files in the respective folders add_subdirectory(developer_tools) add_subdirectory(hash) add_subdirectory(misc) add_subdirectory(games) +add_subdirectory(audio) add_subdirectory(sorting) add_subdirectory(geometry) add_subdirectory(graphics) diff --git a/DIRECTORY.md b/DIRECTORY.md index 3453ca35..0b22b5b9 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -1,5 +1,8 @@ # List of all files +## Audio + * [Alaw](https://github.com/TheAlgorithms/C/blob/master/audio/alaw.c) + ## Client Server * [Client](https://github.com/TheAlgorithms/C/blob/master/client_server/client.c) * [Server](https://github.com/TheAlgorithms/C/blob/master/client_server/server.c) diff --git a/audio/CMakeLists.txt b/audio/CMakeLists.txt new file mode 100644 index 00000000..671d5c66 --- /dev/null +++ b/audio/CMakeLists.txt @@ -0,0 +1,14 @@ +# If necessary, use the RELATIVE flag, otherwise each source file may be listed +# with full pathname. RELATIVE may makes it easier to extract an executable name +# automatically. +file( GLOB APP_SOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.c ) +# file( GLOB APP_SOURCES ${CMAKE_SOURCE_DIR}/*.c ) +# AUX_SOURCE_DIRECTORY(${CMAKE_CURRENT_SOURCE_DIR} APP_SOURCES) +foreach( testsourcefile ${APP_SOURCES} ) + # I used a simple string replace, to cut off .cpp. + string( REPLACE ".c" "" testname ${testsourcefile} ) + add_executable( ${testname} ${testsourcefile} ) + + install(TARGETS ${testname} DESTINATION "bin/audio") + +endforeach( testsourcefile ${APP_SOURCES} ) diff --git a/audio/alaw.c b/audio/alaw.c new file mode 100644 index 00000000..5cf00185 --- /dev/null +++ b/audio/alaw.c @@ -0,0 +1,216 @@ +/** + * @file + * @author [sunzhenliang](https://github.com/HiSunzhenliang) + * @brief A-law algorithm for encoding and decoding (16bit pcm <=> a-law). + * This is the implementation of [G.711](https://en.wikipedia.org/wiki/G.711) + * in C. + **/ + +/** + * Linear input code | Compressed code | Linear output code + * ------------------+-----------------+------------------- + * s0000000abcdx | s000abcd | s0000000abcd1 + * s0000001abcdx | s001abcd | s0000001abcd1 + * s000001abcdxx | s010abcd | s000001abcd10 + * s00001abcdxxx | s011abcd | s00001abcd100 + * s0001abcdxxxx | s100abcd | s0001abcd1000 + * s001abcdxxxxx | s101abcd | s001abcd10000 + * s01abcdxxxxxx | s110abcd | s01abcd100000 + * s1abcdxxxxxxx | s111abcd | s1abcd1000000 + * + * Compressed code: (s | eee | abcd) + **/ +#include /// for assert +#include /// for appropriate size int types +#include /// for IO operations + +/* length of test inputs */ +#define LEN ((size_t)8) + +/* input pcm for test */ +int16_t pcm[LEN] = {1000, -1000, 1234, 3200, -1314, 0, 32767, -32768}; + +/* result coded alaw for test */ +uint8_t r_coded[LEN] = {250, 122, 230, 156, 97, 213, 170, 42}; + +/* result decoded for test */ +int16_t r_decoded[LEN] = {1008, -1008, 1248, 3264, -1312, 8, 32256, -32256}; + +/** + * @brief 16bit pcm to 8bit alaw + * @param out unsigned 8bit alaw array + * @param in signed 16bit pcm array + * @param len length of pcm array + * @returns void + */ +void encode(uint8_t *out, int16_t *in, size_t len) +{ + uint8_t alaw = 0; + int16_t pcm = 0; + int32_t sign = 0; + int32_t abcd = 0; + int32_t eee = 0; + int32_t mask = 0; + for (size_t i = 0; i < len; i++) + { + pcm = *in++; + /* 0-7 kinds of quantization level from the table above */ + eee = 7; + mask = 0x4000; /* 0x4000: '0b0100 0000 0000 0000' */ + + /* Get sign bit */ + sign = (pcm & 0x8000) >> 8; + + /* Turn negative pcm to positive */ + /* The absolute value of a negative number may be larger than the size + * of the corresponding positive number, so here needs `-pcm -1` after + * taking the opposite number. */ + pcm = sign ? (-pcm - 1) : pcm; + + /* Get eee and abcd bit */ + /* Use mask to locate the first `1` bit and quantization level at the + * same time */ + while ((pcm & mask) == 0 && eee > 0) + { + eee--; + mask >>= 1; + } + + /* The location of abcd bits is related with quantization level. Check + * the table above to determine how many bits to `>>` to get abcd */ + abcd = (pcm >> (eee ? (eee + 3) : 4)) & 0x0f; + + /* Put the quantization level number at right bit location to get eee + * bits */ + eee <<= 4; + + /* Splice results */ + alaw = (sign | eee | abcd); + + /* The standard specifies that all resulting even bits (LSB + * is even) are inverted before the octet is transmitted. This is to + * provide plenty of 0/1 transitions to facilitate the clock recovery + * process in the PCM receivers. Thus, a silent A-law encoded PCM + * channel has the 8 bit samples coded 0xD5 instead of 0x80 in the + * octets. (Reference from wiki above) */ + *out++ = alaw ^ 0xD5; + } +} + +/** + * @brief 8bit alaw to 16bit pcm + * @param out signed 16bit pcm array + * @param in unsigned 8bit alaw array + * @param len length of alaw array + * @returns void + */ +void decode(int16_t *out, uint8_t *in, size_t len) +{ + uint8_t alaw = 0; + int32_t pcm = 0; + int32_t sign = 0; + int32_t eee = 0; + for (size_t i = 0; i < len; i++) + { + alaw = *in++; + + /* Re-toggle toggled bits */ + alaw ^= 0xD5; + + /* Get sign bit */ + sign = alaw & 0x80; + + /* Get eee bits */ + eee = (alaw & 0x70) >> 4; + + /* Get abcd bits and add 1/2 quantization step */ + pcm = (alaw & 0x0f) << 4 | 8; + + /* If quantization level > 0, there need `1` bit before abcd bits */ + pcm += eee ? 0x100 : 0x0; + + /* Left shift according quantization level */ + pcm <<= eee > 1 ? (eee - 1) : 0; + + /* Use the right sign */ + *out++ = sign ? -pcm : pcm; + } +} + +/** + * @brief Self-test implementations + * @param pcm signed 16bit pcm array + * @param coded unsigned 8bit alaw array + * @param decoded signed 16bit pcm array + * @param len length of test array + * @returns void + */ +static void test(int16_t *pcm, uint8_t *coded, int16_t *decoded, size_t len) +{ + /* run encode */ + encode(coded, pcm, len); + + /* check encode result */ + for (size_t i = 0; i < len; i++) + { + assert(coded[i] == r_coded[i]); + } + + /* run decode */ + decode(decoded, coded, len); + + /* check decode result */ + for (size_t i = 0; i < len; i++) + { + assert(decoded[i] == r_decoded[i]); + } +} + +/** + * @brief Main function + * @param argc commandline argument count (ignored) + * @param argv commandline array of arguments (ignored) + * @returns 0 on exit + */ +int main(int argc, char *argv[]) +{ + /* output alaw encoded by encode() */ + uint8_t coded[LEN]; + + /* output pcm decoded by decode() from coded[LEN] */ + int16_t decoded[LEN]; + + test(pcm, coded, decoded, LEN); // run self-test implementations + + /* print test pcm inputs */ + printf("inputs: "); + for (size_t i = 0; i < LEN; i++) + { + printf("%d ", pcm[i]); + } + printf("\n"); + + /* print encoded alaw */ + printf("encode: "); + for (size_t i = 0; i < LEN; i++) + { + printf("%u ", coded[i]); + } + printf("\n"); + + /* print decoded pcm */ + printf("decode: "); + for (size_t i = 0; i < LEN; i++) + { + printf("%d ", decoded[i]); + } + printf("\n"); + + /* It can be seen that the encoded alaw is smaller than the input PCM, so + * the purpose of compression is achieved. And the decoded PCM is almost the + * same as the original input PCM, which verifies the correctness of the + * decoding. The reason why it is not exactly the same is that there is + * precision loss during encode / decode. */ + + return 0; +}