Giter VIP home page Giter VIP logo

minimp3's Introduction

minimp3

Build Status Coverity Scan Build Status codecov

Minimalistic, single-header library for decoding MP3. minimp3 is designed to be small, fast (with SSE and NEON support), and accurate (ISO conformant). You can find a rough benchmark below, measured using perf on an i7-6700K, IO included, no CPU heat to address speedstep:

Vector Hz Samples Sec Clockticks Clockticks per second PSNR Max diff
compl.bit 48000 248832 5.184 14306684 2.759M 124.22 1
he_32khz.bit 32000 172800 5.4 8426158 1.560M 139.67 1
he_44khz.bit 44100 472320 10.710 21296300 1.988M 144.04 1
he_48khz.bit 48000 172800 3.6 8453846 2.348M 139.67 1
hecommon.bit 44100 69120 1.567 3169715 2.022M 133.93 1
he_free.bit 44100 156672 3.552 5798418 1.632M 137.48 1
he_mode.bit 44100 262656 5.955 9882314 1.659M 118.00 1
si.bit 44100 135936 3.082 7170520 2.326M 120.30 1
si_block.bit 44100 73728 1.671 4233136 2.533M 125.18 1
si_huff.bit 44100 86400 1.959 4785322 2.442M 107.98 1
sin1k0db.bit 44100 725760 16.457 24842977 1.509M 111.03 1

Conformance test passed on all vectors (PSNR > 96db).

Comparison with keyj's minimp3

Comparison by features:

Keyj minimp3 Current
Fixed point Floating point
source: 84kb 70kb
binary: 34kb (20kb compressed) 30kb (20kb)
no vector opts SSE/NEON intrinsics
no free format free format support

Below, you can find the benchmark and conformance test for keyj's minimp3:

Vector Hz Samples Sec Clockticks Clockticks per second PSNR Max diff
compl.bit 48000 248832 5.184 31849373 6.143M 71.50 41
he_32khz.bit 32000 172800 5.4 26302319 4.870M 71.63 24
he_44khz.bit 44100 472320 10.710 41628861 3.886M 71.63 24
he_48khz.bit 48000 172800 3.6 25899527 7.194M 71.63 24
hecommon.bit 44100 69120 1.567 20437779 13.039M 71.58 25
he_free.bit 44100 0 0 - - - -
he_mode.bit 44100 262656 5.955 30988984 5.203M 71.78 27
si.bit 44100 135936 3.082 24096223 7.817M 72.35 36
si_block.bit 44100 73728 1.671 20722017 12.394M 71.84 26
si_huff.bit 44100 86400 1.959 21121376 10.780M 27.80 65535
sin1k0db.bit 44100 730368 16.561 55569636 3.355M 0.15 58814

Keyj minimp3 conformance test fails on all vectors (PSNR < 96db), and free format is unsupported. This caused some problems when it was used here, and was the main motivation for this work.

Usage

First, we need to initialize the decoder structure:

//#define MINIMP3_ONLY_MP3
//#define MINIMP3_ONLY_SIMD
//#define MINIMP3_NO_SIMD
//#define MINIMP3_NONSTANDARD_BUT_LOGICAL
//#define MINIMP3_FLOAT_OUTPUT
#define MINIMP3_IMPLEMENTATION
#include "minimp3.h"
...
    static mp3dec_t mp3d;
    mp3dec_init(&mp3d);

Note that you must define MINIMP3_IMPLEMENTATION in exactly one source file. You can #include minimp3.h in as many files as you like. Also you can use MINIMP3_ONLY_MP3 define to strip MP1/MP2 decoding code. MINIMP3_ONLY_SIMD define controls generic (non SSE/NEON) code generation (always enabled on x64/arm64 targets). In case you do not want any platform-specific SIMD optimizations, you can define MINIMP3_NO_SIMD. MINIMP3_NONSTANDARD_BUT_LOGICAL define saves some code bytes, and enforces non-standard but logical behaviour of mono-stereo transition (rare case). MINIMP3_FLOAT_OUTPUT makes mp3dec_decode_frame() output to be float instead of short and additional function mp3dec_f32_to_s16 will be available for float->short conversion if needed.

Then. we decode the input stream frame-by-frame:

    /*typedef struct
    {
        int frame_bytes;
        int channels;
        int hz;
        int layer;
        int bitrate_kbps;
    } mp3dec_frame_info_t;*/
    mp3dec_frame_info_t info;
    short pcm[MINIMP3_MAX_SAMPLES_PER_FRAME];
    /*unsigned char *input_buf; - input byte stream*/
    samples = mp3dec_decode_frame(&mp3d, input_buf, buf_size, pcm, &info);

The mp3dec_decode_frame() function decodes one full MP3 frame from the input buffer, which must be large enough to hold one full frame.

The decoder will analyze the input buffer to properly sync with the MP3 stream, and will skip ID3 data, as well as any data which is not valid. Short buffers may cause false sync and can produce 'squealing' artefacts. The bigger the size of the input buffer, the more reliable the sync procedure. We recommend having as many as 10 consecutive MP3 frames (~16KB) in the input buffer at a time.

At end of stream just pass rest of the buffer, sync procedure should work even with just 1 frame in stream (except for free format and garbage at the end can mess things up, so id3v1 and ape tags must be removed first).

For free format there minimum 3 frames needed to do proper sync: 2 frames to detect frame length and 1 next frame to check detect is good.

The size of the consumed MP3 data is returned in the mp3dec_frame_info_t field of the frame_bytes struct; you must remove the data corresponding to the frame_bytes field from the input buffer before the next decoder invocation.

The decoding function returns the number of decoded samples. The following cases are possible:

  • 0: No MP3 data was found in the input buffer
  • 384: Layer 1
  • 576: MPEG 2 Layer 3
  • 1152: Otherwise

The following is a description of the possible combinations of the number of samples and frame_bytes field values:

  • More than 0 samples and frame_bytes > 0: Succesful decode
  • 0 samples and frame_bytes > 0: The decoder skipped ID3 or invalid data
  • 0 samples and frame_bytes == 0: Insufficient data

If frame_bytes == 0, the other fields may be uninitialized or unchanged; if frame_bytes != 0, the other fields are available. The application may call mp3dec_init() when changing decode position, but this is not necessary.

As a special case, the decoder supports already split MP3 streams (for example, after doing an MP4 demux). In this case, the input buffer must contain exactly one non-free-format frame.

Seeking

You can seek to any byte in the stream and call mp3dec_decode_frame; this will work in almost all cases, but is not completely guaranteed. Probablility of sync procedure failure lowers when MAX_FRAME_SYNC_MATCHES value grows. Default MAX_FRAME_SYNC_MATCHES=10 and probablility of sync failure should be very low. If granule data is accidentally detected as a valid MP3 header, short audio artefacting is possible.

High-level mp3dec_ex_seek function supports precise seek to sample (MP3D_SEEK_TO_SAMPLE) using index and binary search.

Track length detect

If the file is known to be cbr, then all frames have equal size and lack ID3 tags, which allows us to decode the first frame and calculate all frame positions as frame_bytes * N. However, because of padding, frames can differ in size even in this case.

In general case whole stream scan is needed to calculate it's length. Scan can be omitted if vbr tag is present (added by encoders like lame and ffmpeg), which contains length info. High-level functions automatically use the vbr tag if present.

High-level API

If you need only decode file/buffer or use precise seek, you can use optional high-level API. Just #include minimp3_ex.h instead and use following additional functions:

#define MP3D_SEEK_TO_BYTE   0
#define MP3D_SEEK_TO_SAMPLE 1

#define MINIMP3_PREDECODE_FRAMES 2 /* frames to pre-decode and skip after seek (to fill internal structures) */
/*#define MINIMP3_SEEK_IDX_LINEAR_SEARCH*/ /* define to use linear index search instead of binary search on seek */
#define MINIMP3_IO_SIZE (128*1024) /* io buffer size for streaming functions, must be greater than MINIMP3_BUF_SIZE */
#define MINIMP3_BUF_SIZE (16*1024) /* buffer which can hold minimum 10 consecutive mp3 frames (~16KB) worst case */
#define MINIMP3_ENABLE_RING 0      /* enable hardware magic ring buffer if available, to make less input buffer memmove(s) in callback IO mode */

#define MP3D_E_MEMORY  -1
#define MP3D_E_IOERROR -2

typedef struct
{
    mp3d_sample_t *buffer;
    size_t samples; /* channels included, byte size = samples*sizeof(mp3d_sample_t) */
    int channels, hz, layer, avg_bitrate_kbps;
} mp3dec_file_info_t;

typedef size_t (*MP3D_READ_CB)(void *buf, size_t size, void *user_data);
typedef int (*MP3D_SEEK_CB)(uint64_t position, void *user_data);

typedef struct
{
    MP3D_READ_CB read;
    void *read_data;
    MP3D_SEEK_CB seek;
    void *seek_data;
} mp3dec_io_t;

typedef struct
{
    uint64_t samples;
    mp3dec_frame_info_t info;
    int last_error;
    ...
} mp3dec_ex_t;

typedef int (*MP3D_ITERATE_CB)(void *user_data, const uint8_t *frame, int frame_size, int free_format_bytes, size_t buf_size, uint64_t offset, mp3dec_frame_info_t *info);
typedef int (*MP3D_PROGRESS_CB)(void *user_data, size_t file_size, uint64_t offset, mp3dec_frame_info_t *info);

/* decode whole buffer block */
int mp3dec_load_buf(mp3dec_t *dec, const uint8_t *buf, size_t buf_size, mp3dec_file_info_t *info, MP3D_PROGRESS_CB progress_cb, void *user_data);
int mp3dec_load_cb(mp3dec_t *dec, mp3dec_io_t *io, uint8_t *buf, size_t buf_size, mp3dec_file_info_t *info, MP3D_PROGRESS_CB progress_cb, void *user_data);
/* iterate through frames */
int mp3dec_iterate_buf(const uint8_t *buf, size_t buf_size, MP3D_ITERATE_CB callback, void *user_data);
int mp3dec_iterate_cb(mp3dec_io_t *io, uint8_t *buf, size_t buf_size, MP3D_ITERATE_CB callback, void *user_data);
/* streaming decoder with seeking capability */
int mp3dec_ex_open_buf(mp3dec_ex_t *dec, const uint8_t *buf, size_t buf_size, int seek_method);
int mp3dec_ex_open_cb(mp3dec_ex_t *dec, mp3dec_io_t *io, int seek_method);
void mp3dec_ex_close(mp3dec_ex_t *dec);
int mp3dec_ex_seek(mp3dec_ex_t *dec, uint64_t position);
size_t mp3dec_ex_read(mp3dec_ex_t *dec, mp3d_sample_t *buf, size_t samples);
#ifndef MINIMP3_NO_STDIO
/* stdio versions of file load, iterate and stream */
int mp3dec_load(mp3dec_t *dec, const char *file_name, mp3dec_file_info_t *info, MP3D_PROGRESS_CB progress_cb, void *user_data);
int mp3dec_iterate(const char *file_name, MP3D_ITERATE_CB callback, void *user_data);
int mp3dec_ex_open(mp3dec_ex_t *dec, const char *file_name, int seek_method);
#ifdef _WIN32
int mp3dec_load_w(mp3dec_t *dec, const wchar_t *file_name, mp3dec_file_info_t *info, MP3D_PROGRESS_CB progress_cb, void *user_data);
int mp3dec_iterate_w(const wchar_t *file_name, MP3D_ITERATE_CB callback, void *user_data);
int mp3dec_ex_open_w(mp3dec_ex_t *dec, const wchar_t *file_name, int seek_method);
#endif
#endif

Use MINIMP3_NO_STDIO define to exclude STDIO functions. MINIMP3_ALLOW_MONO_STEREO_TRANSITION allows mixing mono and stereo in same file. In that case mp3dec_frame_info_t->channels = 0 is reported on such files and correct channels number passed to progress_cb callback for each frame in mp3dec_frame_info_t structure. MP3D_PROGRESS_CB is optional and can be NULL, example of file decoding:

    mp3dec_t mp3d;
    mp3dec_file_info_t info;
    if (mp3dec_load(&mp3d, input_file_name, &info, NULL, NULL))
    {
        /* error */
    }
    /* mp3dec_file_info_t contains decoded samples and info,
       use free(info.buffer) to deallocate samples */

Example of file decoding with seek capability:

    mp3dec_ex_t dec;
    if (mp3dec_ex_open(&dec, input_file_name, MP3D_SEEK_TO_SAMPLE))
    {
        /* error */
    }
    /* dec.samples, dec.info.hz, dec.info.layer, dec.info.channels should be filled */
    if (mp3dec_ex_seek(&dec, position))
    {
        /* error */
    }
    mp3d_sample_t *buffer = malloc(dec.samples*sizeof(mp3d_sample_t));
    size_t readed = mp3dec_ex_read(&dec, buffer, dec.samples);
    if (readed != dec.samples) /* normal eof or error condition */
    {
        if (dec.last_error)
        {
            /* error */
        }
    }

Bindings

Interesting links

minimp3's People

Contributors

a1batross avatar bibendovsky avatar bog-dan-ro avatar bonki avatar dagostinelli avatar kajott avatar kcat avatar kozross avatar lieff avatar mackron avatar manxorist avatar mgeier avatar mvduin avatar nico-abram avatar sagamusix avatar totalcaesar659 avatar yongtang avatar yyny avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

minimp3's Issues

Optional replacement of input buffer with a callback

Like hdt551, I'm hoping to use the decoder on an embedded microcontroller (ARM Cortex M4) but the current memory usage is an issue. The required large size of the input buffer is something that could possibly be eliminated if an optional method that uses a callback function could be implemented. I used this method for another audio decoder and the callback function was called from the get_bits function when more data were needed. The callback function returned a pointer to a buffer with a size of the programmer's choosing along with the number of bytes loaded. These data were then consumed by get_bits until empty and then the callback was called again. This approach would avoid having to load up to the recommend 16K for 10 frames since the data are always on demand.

I did have a look at the source code and I saw a number of problems since get_bits does not appear to be the only thing accessing the data. However I'm not sure how some of it works (such as the inner workings of L3_huffman how all this works with the maindata array) so I don't know if it's easily possible. Would it be easy to do this? I'd be happy to work on this and possibly submit changes but I'd like to know how exactly is the data processed.

Further thoughts:
*If this approach could also enable the removal of the maindata array then that would also be a big win.
**The "callback" function need not be a function pointer, it could simply be a function declared by minimp3.h and the C code that includes the minimp3.h file with the MINIMP3_IMPLEMENTATION definition would implement it.
***If necessary, the function could be allowed to seek forward (or backward if it helps to make the bit reservoir unnecessary) for accessing required data.

Fix clang static analyzer warnings

./minimp3.h:793:17: warning: Assigned value is garbage or undefined
            one = *scf++;
                ^ ~~~~~~
./minimp3.h:841:9: warning: Assigned value is garbage or undefined
        RELOAD_SCALEFACTOR;
        ^~~~~~~~~~~~~~~~~~
./minimp3.h:839:77: note: expanded from macro 'RELOAD_SCALEFACTOR'
#define RELOAD_SCALEFACTOR  if (!--np) { np = *sfb++/2; if (!np) break; one = *scf++; }
                                                                            ^ ~~~~~~
./minimp3.h:954:23: warning: Assigned value is garbage or undefined
        ist_pos[itop] = max_band[i] >= prev ? default_pos : ist_pos[prev];
                      ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Handle the invalid portion instead of sikpping

Hi there,

Thank you for this great codec.

We have a mp3 file which contains one invalid portion.

Checked by mp3val:
WARNING: "C:\work\software\mp3val-0.1.8_with_frontend-0.1.1-bin-win32\bvstts.mp3" (offset 0x630): MPEG stream error, resynchronized successfully
WARNING: "C:\work\software\mp3val-0.1.8_with_frontend-0.1.1-bin-win32\bvstts.mp3": No supported tags in the file
INFO: "C:\work\software\mp3val-0.1.8_with_frontend-0.1.1-bin-win32\bvstts.mp3": 91 MPEG frames (MPEG 2 Layer III), no tags, CBR

We decoded it using minimp3 and found this invalid portion was simply skipped.
I am wondering is there a way to add some "repaired data"?

The following picture:
Track 1: Lame decoded result, fixed the invalid portion
Track 2: minimp3 decoded result, one portion sikkped around 0.35s

window001

The mp3 file: bvstts.zip

Thanks.

minimp3 test against sine.mp3 fails

Running the minimp3 test against a sine wave mp3 generated with Audacity fails with:
rate=44100 samples=0 max_diff=0 PSNR=99.000000
error: mp4 test should decode some samples

The generated sine wave plays without issue on other players.
Using latest commit: 7b2bb87
I hope I am not missing something obvious here.

Warning in minimp3_test.c with clang -Wmissing-field-initializers

-Wmissing-field-initializers is enabled by default with clang -Wall -Wextra

$ clang -O2 -Wall -Wextra -o minimp3 minimp3_test.c -lm
minimp3_test.c:54:37: warning: missing field 'channels' initializer [-Wmissing-field-initializers]
    mp3dec_frame_info_t info = { 0, };
                                    ^
1 warning generated.
$ clang --version
clang version 3.8.1-24 (tags/RELEASE_381/final)
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin
$

neon code fails

Generic ARM code works fine, but when neon enabled - test fails. Failed vectors: ILL2_layer3.bit, l3-compl.bit, l3-he_*.bit.

Possible optimization for mp3d_scale_pcm on ARM

On ARMv6+, the instruction SSAT can be used for clamping. By using the following code, based on my test, yields around 9~10% overall speed improvement on Cortex-M33 with GCC 8.3.1 -O3 with some 128Kbps MP3 files.

Obviously, this approach assumes that the value can fit in int32_t, otherwise it wouldn't work.

static inline int16_t mp3d_scale_pcm(float sample)
{
    int32_t s32 = (int32_t)(sample + .5f);
    s32 -= (s32 < 0);
    int16_t s = (int16_t)__SSAT(s32, 16);
    return s;
}

How to remove extra delay samples?

If I encode wav file (with 87,840,000 samples) to mp3 with Lame then decode it with minimp3, I get extra samples (now 87,841,152) . If I decode with lame or ffmpeg I get the original number of samples back.

Notably when decoding with lame I get

input:  totalFile_t.mp3  (48 kHz, 2 channels, MPEG-1 Layer III)
output: totalFile_t.wav  (16 bit, Microsoft WAVE)
skipping initial 1105 samples (encoder+decoder delay)
skipping final 47 samples (encoder padding-decoder delay)
Frame# 76251/76251  128 kbps   MS

When adding the samples skipped from the delays (1105 + 47), it matches the length of minimp3. How can I calculate the number of samples to skip at the beginning and end to get minimp3 to match the length of the original (ie the output of lame and ffmpeg)?

Questions about usage

Hello,

  1. How can I seek to arbitrary positions? Let say to sample number 'N' (RAW decoded sample, that is)?
  2. How can I initialize the 'info' without decoding any audio?

Thanks!

Last frame + ID3v1 Tag

Hello, I am a newbie to this library and am facing a problem that I'm not sure if it is my fault or the library's fault. I am trying to get the position of the ID3v1 tag in the file, if it exists. I am using the following code - based on the example provided in this repository - to get info regarding an MP3 file:

https://pastebin.com/3gghY7L3
https://pastebin.com/2iQb5FTz

The problem I'm facing is that the ID3v1 position is not set where the TAG starts, but where the last frame starts, which seems to be caused by the function mp3dec_decode_frame returning 0 samples for the last frame when there is an ID3v1 tag in the MP3 file. Any help?

Configurable MAX_FRAME_SYNC_MATCHES

Change
#define MAX_FRAME_SYNC_MATCHES 10
into

#ifndef MAX_FRAME_SYNC_MATCHES
   #define MAX_FRAME_SYNC_MATCHES      10
#endif

Allows to define a custom value before including the library header.

A question about mp3dec_skip_id3

Thanks your great work.
I want abstract you lib as an API to caller who could send any length raw mp3 data to the decoder. There is a case that caller send data is not a complete frame or id3 data. To checking this case, I use
mp3dec_iterate_buf, but a problem occur. when i send a data less than id3 data length, the mp3dec_skip_id3 will modify the buf_size to a value which is not 0 but less than 0, so, the program will run error. I add some log as follow:
send mp3dec_skip_id3, buf_size = 512
after call mp3dec_skip_id3, buf_size = -609 (printf this value as %d)

Could you solve my question, Thank you.

Support building for x86 (32bit)

Unless enabled by compiler option, GCC and clang x86 32bit compilers do not support SSE intrinsics at all, resulting in the following build errors:

$ ./scripts/build.sh
In file included from minimp3_test.c:2:0:
minimp3.h:99:1: error: unknown type name ‘__m128’
 typedef __m128 f4;
 ^
In file included from minimp3_test.c:2:0:
minimp3.h: In function ‘L3_midside_stereo’:
minimp3.h:832:9: warning: implicit declaration of function ‘_mm_loadu_ps’ [-Wimplicit-function-declaration]
         f4 vl = VLD(left + i);
         ^
minimp3.h:834:9: warning: implicit declaration of function ‘_mm_storeu_ps’ [-Wimplicit-function-declaration]
         VSTORE(left + i, VADD(vl, vr));
         ^
minimp3.h:834:9: warning: implicit declaration of function ‘_mm_add_ps’ [-Wimplicit-function-declaration]
minimp3.h:835:9: warning: implicit declaration of function ‘_mm_sub_ps’ [-Wimplicit-function-declaration]
         VSTORE(right + i, VSUB(vl, vr));
         ^
minimp3.h: In function ‘L3_antialias’:
minimp3.h:965:13: warning: implicit declaration of function ‘_mm_shuffle_ps’ [-Wimplicit-function-declaration]
             vd = VREV(vd);
             ^
minimp3.h:965:13: warning: implicit declaration of function ‘_MM_SHUFFLE’ [-Wimplicit-function-declaration]
minimp3.h:966:13: warning: implicit declaration of function ‘_mm_mul_ps’ [-Wimplicit-function-declaration]
             VSTORE(grbuf + 18 + i, VSUB(VMUL(vu, vc0), VMUL(vd, vc1)));
             ^
In file included from minimp3_test.c:2:0:
minimp3.h: In function ‘mp3d_DCT_II’:
minimp3.h:1228:13: warning: implicit declaration of function ‘_mm_set1_ps’ [-Wimplicit-function-declaration]
             f4 t2 = VMUL_S(VSUB(x1, x2), g_sec[3*i + 0]);
             ^
minimp3.h:1272:17: warning: implicit declaration of function ‘_mm_storel_pi’ [-Wimplicit-function-declaration]
                 VSAVE2(0, t[0][i]);
                 ^
minimp3.h:1265:37: error: ‘__m64’ undeclared (first use in this function)
 #define VSAVE2(i, v) _mm_storel_pi((__m64 *)&y[i*18], v)
                                     ^
minimp3.h:1272:17: note: in expansion of macro ‘VSAVE2’
                 VSAVE2(0, t[0][i]);
                 ^
minimp3.h:1265:37: note: each undeclared identifier is reported only once for each function it appears in
 #define VSAVE2(i, v) _mm_storel_pi((__m64 *)&y[i*18], v)
                                     ^
minimp3.h:1272:17: note: in expansion of macro ‘VSAVE2’
                 VSAVE2(0, t[0][i]);
                 ^
minimp3.h:1265:44: error: expected expression before ‘)’ token
 #define VSAVE2(i, v) _mm_storel_pi((__m64 *)&y[i*18], v)
                                            ^
minimp3.h:1272:17: note: in expansion of macro ‘VSAVE2’
                 VSAVE2(0, t[0][i]);
                 ^
minimp3.h:1265:44: error: expected expression before ‘)’ token
 #define VSAVE2(i, v) _mm_storel_pi((__m64 *)&y[i*18], v)
                                            ^
minimp3.h:1273:17: note: in expansion of macro ‘VSAVE2’
                 VSAVE2(1, VADD(t[2][i], s));
                 ^
minimp3.h:1265:44: error: expected expression before ‘)’ token
 #define VSAVE2(i, v) _mm_storel_pi((__m64 *)&y[i*18], v)
                                            ^
minimp3.h:1274:17: note: in expansion of macro ‘VSAVE2’
                 VSAVE2(2, VADD(t[1][i], t[1][i + 1]));
                 ^
minimp3.h:1265:44: error: expected expression before ‘)’ token
 #define VSAVE2(i, v) _mm_storel_pi((__m64 *)&y[i*18], v)
                                            ^
minimp3.h:1275:17: note: in expansion of macro ‘VSAVE2’
                 VSAVE2(3, VADD(t[2][1 + i], s));
                 ^
minimp3.h:1271:20: warning: unused variable ‘s’ [-Wunused-variable]
                 f4 s = VADD(t[3][i], t[3][i + 1]);
                    ^
minimp3.h:1265:44: error: expected expression before ‘)’ token
 #define VSAVE2(i, v) _mm_storel_pi((__m64 *)&y[i*18], v)
                                            ^
minimp3.h:1277:13: note: in expansion of macro ‘VSAVE2’
             VSAVE2(0, t[0][7]);
             ^
minimp3.h:1265:44: error: expected expression before ‘)’ token
 #define VSAVE2(i, v) _mm_storel_pi((__m64 *)&y[i*18], v)
                                            ^
minimp3.h:1278:13: note: in expansion of macro ‘VSAVE2’
             VSAVE2(1, VADD(t[2][7], t[3][7]));
             ^
minimp3.h:1265:44: error: expected expression before ‘)’ token
 #define VSAVE2(i, v) _mm_storel_pi((__m64 *)&y[i*18], v)
                                            ^
minimp3.h:1279:13: note: in expansion of macro ‘VSAVE2’
             VSAVE2(2, t[1][7]);
             ^
minimp3.h:1265:44: error: expected expression before ‘)’ token
 #define VSAVE2(i, v) _mm_storel_pi((__m64 *)&y[i*18], v)
                                            ^
minimp3.h:1280:13: note: in expansion of macro ‘VSAVE2’
             VSAVE2(3, t[3][7]);
             ^
minimp3.h: In function ‘mp3d_synth’:
minimp3.h:1455:13: error: unknown type name ‘__m128i’
             __m128i pcm8 = _mm_packs_epi32(_mm_cvtps_epi32(a), _mm_cvtps_epi32(b));
             ^
minimp3.h:1455:13: warning: implicit declaration of function ‘_mm_packs_epi32’ [-Wimplicit-function-declaration]
minimp3.h:1455:13: warning: implicit declaration of function ‘_mm_cvtps_epi32’ [-Wimplicit-function-declaration]
minimp3.h:1456:13: warning: implicit declaration of function ‘_mm_extract_epi16’ [-Wimplicit-function-declaration]
             dstr[(15 - i)*nch] = _mm_extract_epi16(pcm8, 1);
             ^

The solution is probably to check the appropriate macros that are defined when the required SSE is enabled and only #define HAVE_SIMD 1 when availavle.
See for example https://stackoverflow.com/questions/28939652/how-to-detect-sse-avx-avx2-avx-512-availability-at-compile-time .

Get information about frame offset

Hi there,

Many thanks for the wonderful library.

Would be great if you could allow getting the frame offset in 'mp3dec_decode_frame'

My code changes:

typedef struct
{
-    int frame_bytes, channels, hz, layer, bitrate_kbps;
into->
+    int frame_offset, frame_size, channels, hz, layer, bitrate_kbps; // ESENTHEL CHANGED
} mp3dec_frame_info_t;
-            info->frame_bytes = i;
into
+            info->frame_offset = i; // ESENTHEL CHANGED
+           info->frame_size   = 0; // ESENTHEL CHANGED

and finally:

-    info->frame_bytes = i + frame_size;
into
+    info->frame_offset = i; // ESENTHEL CHANGED
+    info->frame_size   = frame_size; // ESENTHEL CHANGED

this allows to parse the MP3 first, remember each frame exact positions and sizes, so later when decoding, we can read only necesseary data, without having to rely on mp3d_find_frame iterating bytes, and possibly failing, because we've buffered only frame size, without the needed following data to properly detect the frame.

Move static arrays to outside of function bodies

Example:
https://github.com/lieff/minimp3/blob/master/minimp3.h#L267

static unsigned hdr_bitrate_kbps(const uint8_t *h)
{
    static const uint8_t halfrate[2][3][15] = {
        { { 0,4,8,12,16,20,24,28,32,40,48,56,64,72,80 }

There are plenty of static const arrays inside function bodies, however putting them inside functions, incurs a hidden overhead - the array data is actually initialized on the first time the function is called.
So the compiler has to make it look like that

static bool data_initialized=false;
void func()
{
  if(!data_initialized)
  {
     setup array
     data_initialized=true;
   }
  ... here function starts
}

This results in 1 extra byte memory usage for knowing if data was initialized (per function), and what's worst, it causes extra slowdown due to extra "if" inside the function code.

Solution->
move all statics outside of the functions.

    static const uint8_t halfrate[2][3][15] = {
        { { 0,4,8,12,16,20,24,28,32,40,48,56,64,72,80 }
static unsigned hdr_bitrate_kbps(const uint8_t *h)
{

Less memory usage + better performance.

miniaac?

Sorry, this is not an issue with minimp3 but I don't believe there is any other way to ask this question. I would like to use minimp3 on a project but there is no point unless AAC can be decoded as well. The FFmpeg source code for the AAC decoder looks a little terrifying and appears to be vastly more complex than minimp3 but then articles such as this one do not given this impression. So, I was wondering if anyone can answer these questions:

  1. Is decoding AAC actually much more complex than decoding mp3? And are the processing requirements (CPU, RAM, ROM) for decoding AAC much higher? (If so, then how much approximately?)
  2. Are there any small open source AAC decoders available or in the pipeline?
  3. Could minimp3 be extended to support AAC or is AAC too different?

No distinction between skipped ID3 or invalid data

Hi, I'd like to be able to distinguish between a frame that doesn't have all its data and an id3 frame.
I am trying to decode a file that doesn't fit inside RAM (of a microcontroller), so I allocate a buffer of 16384 bytes and try to find the last complete buffer. I pass null pointer as the buffer for the PCM samples. I get a bunch of frames, and then the last one is truncated. I'd like to detect the truncated frame. For the truncated frame I get 0 samples and the following mp3dec_frame_info_t: {frame_bytes: 157, frame_offset: 0, channels: 2, hz: 44100, layer: 3, bitrate_kbps: 128}. So according to the documentation this is "skipped ID3 or invalid data"
If I increase the data size (to 16384 + 1024 bytes) the same frame would read with 1152 samples and mp3dec_frame_info_t{frame_bytes: 418, frame_offset: 0, channels: 2, hz: 44100, layer: 3, bitrate_kbps: 128}

Decoding of the first frame

When a frame following the first frame is decoded it uses the previous frame. How is the first frame decoded, and what is used instead of the previous frame?

Consider using #ifndef-style include guards instead of #pragma once

I'm trying out minimp3 in one of my projects and the use of #pragma once instead of #ifndef-style include guards is a bit annoying:

  1. For projects that are compiled as a single translation unit, #pragma once will not work when they need to keep the header and implementation sections separate (the header section will be included in one part, but due to #pragma once, the implementation section will never be able to be included in the same translation unit).
  2. In the project I'm working on, being able to detect minimp3 at compile time with something like #ifdef MINIMP3_H or something similar would be really useful. (At the moment I'm using #ifdef MINIMP3_MAX_SAMPLES_PER_FRAME which feels a bit silly...)

Another quick note, it's not possible to compile minimp3 and stb_vorbis in the same translation unit because they both define an un-namespaced function called get_bits(). This is actually really inconvenient when writing a decoder abstraction and compiling as a single translation unit, which is what I'm working on.

Thanks for your work on this project!

How is stereo returned?

If I've got it right it returns the left value then the right value. In other words, if I'll reshape it to (-1, 2) then [:, 0] will be the left and [:, 1] will be the right.

Warnings with -Wcast-align

Increasing the required alignment with a pointer cast can result in undefined behaviour (and, in the context of SIMD in particular, result in wrong alignment assumptions by the compiler further down the road) if it indeed turns out misaligned and gets dereferenced.

Clang produces the following warnings on AMD64 when compiling with -Wcast-align. GCC also offers -Wcast-align, however it does not warn here.
I have not yet investigated whether these warnings (in particular warning: cast from 'float *' to '__m64 *' increases required alignment from 4 to 8) are valid or false positives.

Regarding the warnings in minimp3_test.c wav_header(), clang is right. The code in question here also obviously fails on big-endian architectures.

$ clang -O2 -Wall -Wextra -msse2 -Wcast-align -o minimp3 minimp3_test.c -lm
In file included from minimp3_test.c:5:
./minimp3.h:1307:17: warning: cast from 'float *' to '__m64 *' increases required alignment from 4 to 8 [-Wcast-align]
                VSAVE2(0, t[0][i]);
                ^~~~~~~~~~~~~~~~~~
./minimp3.h:1300:36: note: expanded from macro 'VSAVE2'
#define VSAVE2(i, v) _mm_storel_pi((__m64 *)&y[i*18], v)
                                   ^~~~~~~~~~~~~~~~~
./minimp3.h:1308:17: warning: cast from 'float *' to '__m64 *' increases required alignment from 4 to 8 [-Wcast-align]
                VSAVE2(1, VADD(t[2][i], s));
                ^~~~~~~~~~~~~~~~~~~~~~~~~~~
./minimp3.h:1300:36: note: expanded from macro 'VSAVE2'
#define VSAVE2(i, v) _mm_storel_pi((__m64 *)&y[i*18], v)
                                   ^~~~~~~~~~~~~~~~~
./minimp3.h:1309:17: warning: cast from 'float *' to '__m64 *' increases required alignment from 4 to 8 [-Wcast-align]
                VSAVE2(2, VADD(t[1][i], t[1][i + 1]));
                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
./minimp3.h:1300:36: note: expanded from macro 'VSAVE2'
#define VSAVE2(i, v) _mm_storel_pi((__m64 *)&y[i*18], v)
                                   ^~~~~~~~~~~~~~~~~
./minimp3.h:1310:17: warning: cast from 'float *' to '__m64 *' increases required alignment from 4 to 8 [-Wcast-align]
                VSAVE2(3, VADD(t[2][1 + i], s));
                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
./minimp3.h:1300:36: note: expanded from macro 'VSAVE2'
#define VSAVE2(i, v) _mm_storel_pi((__m64 *)&y[i*18], v)
                                   ^~~~~~~~~~~~~~~~~
./minimp3.h:1312:13: warning: cast from 'float *' to '__m64 *' increases required alignment from 4 to 8 [-Wcast-align]
            VSAVE2(0, t[0][7]);
            ^~~~~~~~~~~~~~~~~~
./minimp3.h:1300:36: note: expanded from macro 'VSAVE2'
#define VSAVE2(i, v) _mm_storel_pi((__m64 *)&y[i*18], v)
                                   ^~~~~~~~~~~~~~~~~
./minimp3.h:1313:13: warning: cast from 'float *' to '__m64 *' increases required alignment from 4 to 8 [-Wcast-align]
            VSAVE2(1, VADD(t[2][7], t[3][7]));
            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
./minimp3.h:1300:36: note: expanded from macro 'VSAVE2'
#define VSAVE2(i, v) _mm_storel_pi((__m64 *)&y[i*18], v)
                                   ^~~~~~~~~~~~~~~~~
./minimp3.h:1314:13: warning: cast from 'float *' to '__m64 *' increases required alignment from 4 to 8 [-Wcast-align]
            VSAVE2(2, t[1][7]);
            ^~~~~~~~~~~~~~~~~~
./minimp3.h:1300:36: note: expanded from macro 'VSAVE2'
#define VSAVE2(i, v) _mm_storel_pi((__m64 *)&y[i*18], v)
                                   ^~~~~~~~~~~~~~~~~
./minimp3.h:1315:13: warning: cast from 'float *' to '__m64 *' increases required alignment from 4 to 8 [-Wcast-align]
            VSAVE2(3, t[3][7]);
            ^~~~~~~~~~~~~~~~~~
./minimp3.h:1300:36: note: expanded from macro 'VSAVE2'
#define VSAVE2(i, v) _mm_storel_pi((__m64 *)&y[i*18], v)
                                   ^~~~~~~~~~~~~~~~~
minimp3_test.c:22:6: warning: cast from 'char *' to 'int *' increases required alignment from 1 to 4 [-Wcast-align]
    *(int *  )(hdr + 0x04) = 44 + data_bytes - 8;   /* File size - 8 */
     ^~~~~~~~~~~~~~~~~~~~~
minimp3_test.c:23:6: warning: cast from 'char *' to 'short *' increases required alignment from 1 to 2 [-Wcast-align]
    *(short *)(hdr + 0x14) = 1;                     /* Integer PCM format */
     ^~~~~~~~~~~~~~~~~~~~~
minimp3_test.c:24:6: warning: cast from 'char *' to 'short *' increases required alignment from 1 to 2 [-Wcast-align]
    *(short *)(hdr + 0x16) = ch;
     ^~~~~~~~~~~~~~~~~~~~~
minimp3_test.c:25:6: warning: cast from 'char *' to 'int *' increases required alignment from 1 to 4 [-Wcast-align]
    *(int *  )(hdr + 0x18) = hz;
     ^~~~~~~~~~~~~~~~~~~~~
minimp3_test.c:26:6: warning: cast from 'char *' to 'int *' increases required alignment from 1 to 4 [-Wcast-align]
    *(int *  )(hdr + 0x1C) = nAvgBytesPerSec;
     ^~~~~~~~~~~~~~~~~~~~~
minimp3_test.c:27:6: warning: cast from 'char *' to 'short *' increases required alignment from 1 to 2 [-Wcast-align]
    *(short *)(hdr + 0x20) = nBlockAlign;
     ^~~~~~~~~~~~~~~~~~~~~
minimp3_test.c:28:6: warning: cast from 'char *' to 'short *' increases required alignment from 1 to 2 [-Wcast-align]
    *(short *)(hdr + 0x22) = bips;
     ^~~~~~~~~~~~~~~~~~~~~
minimp3_test.c:29:6: warning: cast from 'char *' to 'int *' increases required alignment from 1 to 4 [-Wcast-align]
    *(int *  )(hdr + 0x28) = data_bytes;
     ^~~~~~~~~~~~~~~~~~~~~
minimp3_test.c:54:37: warning: missing field 'channels' initializer [-Wmissing-field-initializers]
    mp3dec_frame_info_t info = { 0, };
                                    ^
minimp3_test.c:74:60: warning: cast from 'const unsigned char *' to 'short *' increases required alignment from 1 to 2 [-Wcast-align]
                    int MSEtemp = abs((int)pcm[i] - (int)(((short*)buf_ref)[i]));
                                                           ^~~~~~~~~~~~~~~
18 warnings generated.

Process mp3 file in small buffer segments

Hi lieff,

I am trying to use your decoder on an embedded microcontroller.
That means I must process the mp3 file in small increments. I am
currently using a 4096 buffer. The difficulty I believe is when the
beginning of one frame is in the current buffer and the rest of it
is in the yet-to-be-read file. I should be able to do this by
moving the unprocessed data from the current buffer and combining
it with new data when I refill the buffer.

Specific example I am working on:
first buffer (4096 bytes), does not contain a valid frame and
the result of:
samples=mp3dec_decode_frame(&mp3d, readPtr, g_bytesLeftInBuffer, g_pcmFromMp3Decoder,&info)
is:
samples=0, info.frame_bytes=4092
at this point I:
if (samples==0 && info.frame_bytes>0 && (g_bytesLeftInBuffer-info.frame_bytes==4)) {
readPtr += info.frame_bytes;
g_bytesLeftInBuffer-=info.frame_bytes;
memmove(g_fileReadBuffer, readPtr, g_bytesLeftInBuffer);
bytesActuallyGotten=fread(g_fileReadBuffer+g_bytesLeftInBuffer,1,FILE_READ_BUFFER_SIZE - g_bytesLeftInBuffer,f_ptr); // fill the buffer again
readPtr=g_fileReadBuffer;
g_bytesLeftInBuffer=g_bytesLeftInBuffer + bytesActuallyGotten;
}

the next run of:
samples=mp3dec_decode_frame(&mp3d, readPtr, g_bytesLeftInBuffer, g_pcmFromMp3Decoder,&info)
gives me:
samples=1152, info.frame_bytes=666
I then do:
readPtr += info.frame_bytes;
g_bytesLeftInBuffer-=info.frame_bytes;
and will continue to work fine until I near the end of the 4096 bytes.
At the point when g_bytesLeftInBuffer=295
samples=0, info.frame_bytes=291
Then I do this to move the unprocessed data back to the beginning of the
g_fileReadbuffer and then fill it back up with data:
readPtr += info.frame_bytes;
g_bytesLeftInBuffer-=info.frame_bytes;
memmove(g_fileReadBuffer, readPtr, g_bytesLeftInBuffer);
bytesActuallyGotten=fread(g_fileReadBuffer+g_bytesLeftInBuffer,1,FILE_READ_BUFFER_SIZE - g_bytesLeftInBuffer,f_ptr);
readPtr=g_fileReadBuffer;
g_bytesLeftInBuffer=g_bytesLeftInBuffer + bytesActuallyGotten;

At this point, running:
samples=mp3dec_decode_frame(&mp3d, readPtr, g_bytesLeftInBuffer, g_pcmFromMp3Decoder,&info)
no longer works.
and for instance
samples=0, info.frame_bytes=963

Which I suppose is because I have messed up the memmove() and fread().

I cannot seem to tell where the next start-of-frame is to know what to memmove()
back to the beginning of g_fileReadBuffer for the next buffer read. I think this
is because I have the beginning of a frame towards the end of my buffer and the
rest of the frame will be in the next read. Of course I want to move the unprocessed
part of the frame into the next buffer read and then finish filling it with new data
.

It looks like information from info.frame_bytes or mp3d_find_frame()
doesn't necessarily tell you where the start of frame is?
Also very possible I am simply not using it correctly.

Thanks for any suggestions.

Allow float point output

WebAudio supports only floats, so it may be good allow float output without float->short->float conversion (using define switch).

Fix conversion warnings

Please fix all of -Wconversion warnings in minimp3 code. Most of them are non-critical, but fixing them is a way to robust stable code.

Handle zero-tail in buffers.

For example scratch.grbuf usually not fully filled. We can use fullness information instead of process bunch of zeros.

How to perform sample-precision seeking?

I'm implementing chunked decoding of mp3 files (for now they're decoding in a single float buffer, that produces ~1Gb PCM data for ~50min mp3, so that's unacceptable for e.g. mobile devices).
I have implemented "keypoints" save during initial analysing (decoding entire file without PCM output so it's fast), saving a file and sample offset each ~30sec. But when I seek to file position and call mp3dec_decode_frame, it produces zero-length output for first mp3 data frame (the next frames decodes successfully), so this way is produces sound glitch each ~30sec.

Further optimize L3_huffman and L3_imdct36

ARM Instructions profile:

Total executed instructions: 2164536044
L3_huffman.isra.2 685600678 31.674%
mp3d_synth 546698880 25.257%
L3_imdct36 251612240 11.624%
L3_dct3_9 176638976 8.161%
mp3d_DCT_II 165811968 7.660%
mp3d_synth_pair 61793280 2.855%
L3_antialias 48054640 2.220%
L3_change_sign 36160512 1.671%
L3_midside_stereo 27845120 1.286%
get_bits 27395265 1.266%
memset 26390774 1.219%
mp3d_scale_pcm 21970944 1.015%
__memcpy_neon 19825566 0.916%
L3_ldexp_q2 17661260 0.816%
L3_read_scalefactors 14160764 0.654%
L3_decode_scalefactors 10988038 0.508%

L3_huffman and L3_imdct36+L3_dct3_9 needs optimizations. (Vectorize two L3_dct3_9?)

Extract unquantized MDCT frames

For machine learning applications where models learn on spectral input representations, it would be handy to be able to load the unquantized MDCT frames without full decoding. That why audio loading speed can be greatly reduced.

How could this be achieved using minimp3?

Best way to determine length in seconds on vbr mp3

Hi,

First, thank you very much for an excellent mp3 decoder! It is working very well for me.

In my program, I show the length (in time) of the mp3 playing and this can be calculated easily when the mp3 is a constant bitrate, but not so easy when the bitrate is variable. My only idea is to average the bitrate of the first maybe 10 frames and use that. Probably be close but maybe there is a better technique for this (other than reading the entire file first)?

Thanks for any suggestions.

Seeking issues

Hello!

There're no way to contact to author, so I have to use bugtracker for that:)

I use my own mpeg stream parser, so minimp3 operates with (offset,size) pairs over the raw data.
There's a some problem with mp3dec_decode_frame problem while seeking. It returns 0 samples with proper frame_bytes at the first non-sequential frame decoding. Next call over the same frame also gives the same result. BUT third call works well with glitchy sound at the beginning.
Some additional information:

  1. there's memset(&scratch, 0, sizeof(scratch)); patch at the beginning of mp3dec_decode_frame - else valgrind warns about unitialized memory usage (but issue is reproduced even without this patch)
  2. mp3dec_init is called before non-sequential frame decoding (no change if removed)
  3. decoding over several frames' raw data (as recommended in readme) has no any effect

Thank you for any help!

Separating minimp3.h file to source and header

Hi,
First of all, thanks for the wonderful, low footprint and C based mp3 decoder you have developed for public domain.
My question is: "Are you planning to separate minimp3.h into corresponding source files and header files in future according to their functionality?"
That would be useful when analysing minimp3 as a standalone library component. For example: if I want to analyse the memory (flash + RAM) used by minimp3 on my controller, and if I have all components of minimp3 separated as .c and .h files, then I can create a different library for minimp3 using its own makefile and find out the text+RO data size, code size, RAM requirement etc. very easily. Apart from that, it will also improve the code organization, redability and understandibility.
Can you let me know if you are planning on it or not?

Checking for invalid input files

Hi. It was brought to my attention a minor issue recently: it's possible for mp3dec_load to return 0 for success, yet giving a result having zero channels.

The software user tried to open a junk file as sound sample, the channels=0 situation caused FPE shortly after the minimp3 decoding. While the workaround is trivial at my side, it may be better to handle this checking at library level.

https://github.com/linuxmao-org/Frontieres/blob/a1cdcdb6b4531efca4f83f0483b08926134c4fc5/sources/model/Sample.cpp#L294-L300

Any files of text kind I sent into the decoder have resulted in the identical 0-channel situation. (readmes and such)

(on a side note, thanks for making the excellent library 👍)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.