DOSBox-X
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Defines
src/libs/decoders/mp3.cpp
00001 /*
00002  *  SPDX-License-Identifier: GPL-2.0-or-later
00003  *
00004  *  Copyright (C) 2001-2017  Ryan C. Gordon <icculus@icculus.org>
00005  *  Copyright (C) 2018-2019  Kevin R. Croft <krcroft@gmail.com>
00006  *  Copyright (C) 2020-2020  The DOSBox-X project
00007  *
00008  *  This program is free software; you can redistribute it and/or modify
00009  *  it under the terms of the GNU General Public License as published by
00010  *  the Free Software Foundation; either version 2 of the License, or
00011  *  (at your option) any later version.
00012  *
00013  *  This program is distributed in the hope that it will be useful,
00014  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00015  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00016  *  GNU General Public License for more details.
00017  *
00018  *  You should have received a copy of the GNU General Public License along
00019  *  with this program; if not, write to the Free Software Foundation, Inc.,
00020  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
00021  */
00022 
00023 /*
00024  *  DOSBox-X MP3 decoder API implementation
00025  *  ---------------------------------------
00026  *  This decoder makes use of the dr_mp3 library by David Reid (mackron@gmail.com)
00027  *    - dr_libs: https://github.com/mackron/dr_libs (source)
00028  *    - dr_mp3:  http://mackron.github.io/dr_mp3.html (website)
00029  */
00030 #include <support.h>
00031 
00032 //#include "mp3_seek_table.h"
00033 #define DR_MP3_IMPLEMENTATION
00034 #include "dr_mp3.h"
00035 
00036 #include "SDL_sound.h"
00037 #define __SDL_SOUND_INTERNAL__
00038 #include "SDL_sound_internal.h"
00039 
00040 static constexpr char fast_seek_filename[] = "fastseek.lut";
00041 
00042 static size_t mp3_read(void* const pUserData, void* const pBufferOut, const size_t bytesToRead)
00043 {
00044     Uint8* ptr = static_cast<Uint8*>(pBufferOut);
00045     Sound_Sample* const sample = static_cast<Sound_Sample*>(pUserData);
00046     const Sound_SampleInternal* const internal = static_cast<const Sound_SampleInternal*>(sample->opaque);
00047     SDL_RWops* rwops = internal->rw;
00048     size_t bytes_read = 0;
00049 
00050     while (bytes_read < bytesToRead)
00051     {
00052         const size_t rc = SDL_RWread(rwops, ptr, 1, bytesToRead - bytes_read);
00053         if (rc == 0) {
00054             sample->flags |= SOUND_SAMPLEFLAG_EOF;
00055             break;
00056         } /* if */
00057         bytes_read += rc;
00058         ptr += rc;
00059     } /* while */
00060 
00061     return bytes_read;
00062 } /* mp3_read */
00063 
00064 static drmp3_bool32 mp3_seek(void* const pUserData, const Sint32 offset, const drmp3_seek_origin origin)
00065 {
00066     const Sint32 whence = (origin == drmp3_seek_origin_start) ? RW_SEEK_SET : RW_SEEK_CUR;
00067     Sound_Sample* const sample = static_cast<Sound_Sample*>(pUserData);
00068     Sound_SampleInternal* const internal = static_cast<Sound_SampleInternal*>(sample->opaque);
00069     return (SDL_RWseek(internal->rw, offset, whence) != -1) ? DRMP3_TRUE : DRMP3_FALSE;
00070 } /* mp3_seek */
00071 
00072 
00073 static Sint32 MP3_init(void)
00074 {
00075     return 1;  /* always succeeds. */
00076 } /* MP3_init */
00077 
00078 
00079 static void MP3_quit(void)
00080 {
00081     /* it's a no-op. */
00082 } /* MP3_quit */
00083 
00084 static void MP3_close(Sound_Sample* const sample)
00085 {
00086     Sound_SampleInternal* const internal = static_cast<Sound_SampleInternal*>(sample->opaque);
00087     mp3_t* p_mp3 = static_cast<mp3_t*>(internal->decoder_private);
00088     if (p_mp3) {
00089         delete p_mp3->p_dr;
00090         p_mp3->p_dr = nullptr;
00091         delete p_mp3;
00092         internal->decoder_private = nullptr;
00093     }
00094 } /* MP3_close */
00095 
00096 static Uint32 MP3_read(Sound_Sample* const sample, void* buffer, Uint32 desired_frames)
00097 {
00098     Sound_SampleInternal* const internal = static_cast<Sound_SampleInternal*>(sample->opaque);
00099     mp3_t* p_mp3 = static_cast<mp3_t*>(internal->decoder_private);
00100 
00101     // LOG_MSG("read-while: num_frames: %u", num_frames);
00102     return static_cast<Uint32>(drmp3_read_pcm_frames_s16(p_mp3->p_dr,
00103                                                          static_cast<drmp3_uint64>(desired_frames),
00104                                                          static_cast<drmp3_int16*>(buffer)));
00105 } /* MP3_read */
00106 
00107 static int32_t MP3_open(Sound_Sample* const sample, const char* const ext)
00108 {
00109     (void) ext; // deliberately unused
00110     Sound_SampleInternal* const internal = static_cast<Sound_SampleInternal*>(sample->opaque);
00111 
00112     // Allocate our structures to be held by sample
00113     mp3_t* p_mp3 = new mp3_t;
00114     p_mp3->p_dr = new drmp3;
00115 
00116     // Open the MP3
00117     if (drmp3_init(p_mp3->p_dr, mp3_read, mp3_seek, sample, nullptr) != DRMP3_TRUE) {
00118         SNDDBG(("MP3: Failed to open the data stream.\n"));
00119         delete p_mp3->p_dr;
00120         delete p_mp3;
00121         return 0; // failure
00122     }
00123 
00124     // Assign our internal decoder to the mp3 object we've just opened
00125     internal->decoder_private = p_mp3;
00126 
00127     bool result;
00128     // Count the MP3's frames
00129     const uint64_t num_frames = populate_seek_points(internal->rw, p_mp3, fast_seek_filename, result);
00130     if (!result) {
00131         SNDDBG(("MP3: Unable to count the number of PCM frames.\n"));
00132         MP3_close(sample);
00133         return 0; // failure
00134     }
00135 
00136     // Populate the sample's properties from the MP3
00137     SNDDBG(("MP3: Accepting data stream.\n"));
00138     sample->flags = SOUND_SAMPLEFLAG_CANSEEK;
00139     sample->actual.channels = static_cast<uint8_t>(p_mp3->p_dr->channels);
00140     sample->actual.rate = p_mp3->p_dr->sampleRate;
00141     sample->actual.format = AUDIO_S16SYS;  // native byte-order based on architecture
00142     // total_time needs milliseconds
00143     internal->total_time = static_cast<int32_t>(ceil_udivide(num_frames * 1000u, sample->actual.rate));
00144     return 1; // success
00145 } /* MP3_open */
00146 
00147 static Sint32 MP3_rewind(Sound_Sample* const sample)
00148 {
00149     Sound_SampleInternal* const internal = static_cast<Sound_SampleInternal*>(sample->opaque);
00150     mp3_t* p_mp3 = static_cast<mp3_t*>(internal->decoder_private);
00151     return (drmp3_seek_to_start_of_stream(p_mp3->p_dr) == DRMP3_TRUE);
00152 } /* MP3_rewind */
00153 
00154 static Sint32 MP3_seek(Sound_Sample* const sample, const Uint32 ms)
00155 {
00156     Sound_SampleInternal* const internal = static_cast<Sound_SampleInternal*>(sample->opaque);
00157     mp3_t* p_mp3 = static_cast<mp3_t*>(internal->decoder_private);
00158     const uint64_t sample_rate = sample->actual.rate;
00159     const drmp3_uint64 pcm_frame = ceil_udivide(sample_rate * ms, 1000u);
00160     const drmp3_bool32 result = drmp3_seek_to_pcm_frame(p_mp3->p_dr, pcm_frame);
00161     return (result == DRMP3_TRUE);
00162 } /* MP3_seek */
00163 
00164 /* dr_mp3 will play layer 1 and 2 files, too */
00165 static const char* extensions_mp3[] = { "MP3", "MP2", "MP1", nullptr };
00166 
00167 extern const Sound_DecoderFunctions __Sound_DecoderFunctions_MP3 = {
00168     {
00169         extensions_mp3,
00170         "MPEG-1 Audio Layer I-III",
00171         "The DOSBox-X project"
00172     },
00173 
00174     MP3_init,       /*   init() method */
00175     MP3_quit,       /*   quit() method */
00176     MP3_open,       /*   open() method */
00177     MP3_close,      /*  close() method */
00178     MP3_read,       /*   read() method */
00179     MP3_rewind,     /* rewind() method */
00180     MP3_seek        /*   seek() method */
00181 };
00182 /* end of SDL_sound_mp3.c ... */