From 4590a98f0eb9484e185e7e0c25a3373c8e9076ea Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Tue, 26 Aug 2008 08:27:05 +0200 Subject: added audio_format parameter to decoder_initialized() dc->audioFormat is set once by the decoder plugins before invoking decoder_initialized(); hide dc->audioFormat and let the decoder pass an AudioFormat pointer to decoder_initialized(). --- src/decoder_api.c | 9 ++++++++- src/decoder_api.h | 3 ++- src/inputPlugins/_flac_common.c | 7 +++---- src/inputPlugins/_flac_common.h | 1 + src/inputPlugins/aac_plugin.c | 11 +++++------ src/inputPlugins/audiofile_plugin.c | 21 +++++++++++---------- src/inputPlugins/flac_plugin.c | 10 +++++----- src/inputPlugins/mod_plugin.c | 15 ++++++++------- src/inputPlugins/mp3_plugin.c | 6 +++--- src/inputPlugins/mp4_plugin.c | 15 +++++++-------- src/inputPlugins/mpc_plugin.c | 21 ++++++++++----------- src/inputPlugins/oggflac_plugin.c | 12 ++++++------ src/inputPlugins/oggvorbis_plugin.c | 13 ++++++------- src/inputPlugins/wavpack_plugin.c | 27 +++++++++++++-------------- 14 files changed, 88 insertions(+), 83 deletions(-) diff --git a/src/decoder_api.c b/src/decoder_api.c index 99f7293bb..9583c7493 100644 --- a/src/decoder_api.c +++ b/src/decoder_api.c @@ -24,10 +24,17 @@ #include "playerData.h" #include "gcc.h" -void decoder_initialized(mpd_unused struct decoder * decoder) +void decoder_initialized(mpd_unused struct decoder * decoder, + const AudioFormat * audio_format) { assert(dc.state == DECODE_STATE_START); + if (audio_format != NULL) { + dc.audioFormat = *audio_format; + getOutputAudioFormat(audio_format, + &(ob.audioFormat)); + } + dc.state = DECODE_STATE_DECODE; notify_signal(&pc.notify); } diff --git a/src/decoder_api.h b/src/decoder_api.h index a4fd7fc97..eb2ca3887 100644 --- a/src/decoder_api.h +++ b/src/decoder_api.h @@ -40,7 +40,8 @@ struct decoder; * Notify the player thread that it has finished initialization and * that it has read the song's meta data. */ -void decoder_initialized(struct decoder * decoder); +void decoder_initialized(struct decoder * decoder, + const AudioFormat * audio_format); /** * This function is called by the decoder plugin when it has diff --git a/src/inputPlugins/_flac_common.c b/src/inputPlugins/_flac_common.c index e1017e596..c48f43da9 100644 --- a/src/inputPlugins/_flac_common.c +++ b/src/inputPlugins/_flac_common.c @@ -162,11 +162,10 @@ void flac_metadata_common_cb(const FLAC__StreamMetadata * block, switch (block->type) { case FLAC__METADATA_TYPE_STREAMINFO: - dc.audioFormat.bits = (mpd_sint8)si->bits_per_sample; - dc.audioFormat.sampleRate = si->sample_rate; - dc.audioFormat.channels = (mpd_sint8)si->channels; + data->audio_format.bits = (mpd_sint8)si->bits_per_sample; + data->audio_format.sampleRate = si->sample_rate; + data->audio_format.channels = (mpd_sint8)si->channels; dc.totalTime = ((float)si->total_samples) / (si->sample_rate); - getOutputAudioFormat(&(dc.audioFormat), &(ob.audioFormat)); break; case FLAC__METADATA_TYPE_VORBIS_COMMENT: flacParseReplayGain(block, data); diff --git a/src/inputPlugins/_flac_common.h b/src/inputPlugins/_flac_common.h index 9eb916b8c..e87ae9307 100644 --- a/src/inputPlugins/_flac_common.h +++ b/src/inputPlugins/_flac_common.h @@ -143,6 +143,7 @@ typedef struct { size_t chunk_length; float time; unsigned int bitRate; + AudioFormat audio_format; FLAC__uint64 position; struct decoder *decoder; InputStream *inStream; diff --git a/src/inputPlugins/aac_plugin.c b/src/inputPlugins/aac_plugin.c index 8765dc21c..4fa54c646 100644 --- a/src/inputPlugins/aac_plugin.c +++ b/src/inputPlugins/aac_plugin.c @@ -286,6 +286,7 @@ static int aac_decode(struct decoder * mpd_decoder, char *path) faacDecFrameInfo frameInfo; faacDecConfigurationPtr config; long bread; + AudioFormat audio_format; uint32_t sampleRate; unsigned char channels; int eof = 0; @@ -335,7 +336,7 @@ static int aac_decode(struct decoder * mpd_decoder, char *path) return -1; } - dc.audioFormat.bits = 16; + audio_format.bits = 16; dc.totalTime = totalTime; @@ -369,11 +370,9 @@ static int aac_decode(struct decoder * mpd_decoder, char *path) #endif if (dc.state != DECODE_STATE_DECODE) { - dc.audioFormat.channels = frameInfo.channels; - dc.audioFormat.sampleRate = sampleRate; - getOutputAudioFormat(&(dc.audioFormat), - &(ob.audioFormat)); - decoder_initialized(mpd_decoder); + audio_format.channels = frameInfo.channels; + audio_format.sampleRate = sampleRate; + decoder_initialized(mpd_decoder, &audio_format); } advanceAacBuffer(&b, frameInfo.bytesconsumed); diff --git a/src/inputPlugins/audiofile_plugin.c b/src/inputPlugins/audiofile_plugin.c index 696621aff..f96ea2fca 100644 --- a/src/inputPlugins/audiofile_plugin.c +++ b/src/inputPlugins/audiofile_plugin.c @@ -45,6 +45,7 @@ static int audiofile_decode(struct decoder * decoder, char *path) int fs, frame_count; AFfilehandle af_fp; int bits; + AudioFormat audio_format; mpd_uint16 bitRate; struct stat st; @@ -62,30 +63,30 @@ static int audiofile_decode(struct decoder * decoder, char *path) afSetVirtualSampleFormat(af_fp, AF_DEFAULT_TRACK, AF_SAMPFMT_TWOSCOMP, 16); afGetVirtualSampleFormat(af_fp, AF_DEFAULT_TRACK, &fs, &bits); - dc.audioFormat.bits = (mpd_uint8)bits; - dc.audioFormat.sampleRate = + audio_format.bits = (mpd_uint8)bits; + audio_format.sampleRate = (unsigned int)afGetRate(af_fp, AF_DEFAULT_TRACK); - dc.audioFormat.channels = + audio_format.channels = (mpd_uint8)afGetVirtualChannels(af_fp, AF_DEFAULT_TRACK); - getOutputAudioFormat(&(dc.audioFormat), &(ob.audioFormat)); frame_count = afGetFrameCount(af_fp, AF_DEFAULT_TRACK); dc.totalTime = - ((float)frame_count / (float)dc.audioFormat.sampleRate); + ((float)frame_count / (float)audio_format.sampleRate); bitRate = (mpd_uint16)(st.st_size * 8.0 / dc.totalTime / 1000.0 + 0.5); - if (dc.audioFormat.bits != 8 && dc.audioFormat.bits != 16) { + if (audio_format.bits != 8 && audio_format.bits != 16) { ERROR("Only 8 and 16-bit files are supported. %s is %i-bit\n", - path, dc.audioFormat.bits); + path, audio_format.bits); afCloseFile(af_fp); return -1; } fs = (int)afGetVirtualFrameSize(af_fp, AF_DEFAULT_TRACK, 1); - decoder_initialized(decoder); + decoder_initialized(decoder, &audio_format); + { int ret, eof = 0, current = 0; char chunk[CHUNK_SIZE]; @@ -94,7 +95,7 @@ static int audiofile_decode(struct decoder * decoder, char *path) if (dc.command == DECODE_COMMAND_SEEK) { decoder_clear(decoder); current = dc.seekWhere * - dc.audioFormat.sampleRate; + audio_format.sampleRate; afSeekFrame(af_fp, AF_DEFAULT_TRACK, current); dc_command_finished(); } @@ -110,7 +111,7 @@ static int audiofile_decode(struct decoder * decoder, char *path) 1, chunk, ret * fs, (float)current / - (float)dc.audioFormat. + (float)audio_format. sampleRate, bitRate, NULL); if (dc.command == DECODE_COMMAND_STOP) diff --git a/src/inputPlugins/flac_plugin.c b/src/inputPlugins/flac_plugin.c index 4bfc87246..f5d5469f7 100644 --- a/src/inputPlugins/flac_plugin.c +++ b/src/inputPlugins/flac_plugin.c @@ -242,7 +242,7 @@ static FLAC__StreamDecoderWriteStatus flacWrite(const flac_decoder *dec, FLAC__uint32 samples = frame->header.blocksize; unsigned int c_samp; const unsigned int num_channels = frame->header.channels; - const unsigned int bytes_per_sample = (dc.audioFormat.bits / 8); + const unsigned int bytes_per_sample = (data->audio_format.bits / 8); const unsigned int bytes_per_channel = bytes_per_sample * frame->header.channels; const unsigned int max_samples = FLAC_CHUNK_SIZE / bytes_per_channel; @@ -250,7 +250,7 @@ static FLAC__StreamDecoderWriteStatus flacWrite(const flac_decoder *dec, float timeChange; FLAC__uint64 newPosition = 0; - assert(dc.audioFormat.bits > 0); + assert(data->audio_format.bits > 0); timeChange = ((float)samples) / frame->header.sample_rate; data->time += timeChange; @@ -415,7 +415,7 @@ static int flac_decode_internal(struct decoder * decoder, } } - decoder_initialized(decoder); + decoder_initialized(decoder, &data.audio_format); while (1) { if (!flac_process_single(flacDec)) @@ -424,11 +424,11 @@ static int flac_decode_internal(struct decoder * decoder, break; if (dc.command == DECODE_COMMAND_SEEK) { FLAC__uint64 sampleToSeek = dc.seekWhere * - dc.audioFormat.sampleRate + 0.5; + data.audio_format.sampleRate + 0.5; if (flac_seek_absolute(flacDec, sampleToSeek)) { decoder_clear(decoder); data.time = ((float)sampleToSeek) / - dc.audioFormat.sampleRate; + data.audio_format.sampleRate; data.position = 0; } else dc.seekError = 1; diff --git a/src/inputPlugins/mod_plugin.c b/src/inputPlugins/mod_plugin.c index 930b041e9..4ab1338bf 100644 --- a/src/inputPlugins/mod_plugin.c +++ b/src/inputPlugins/mod_plugin.c @@ -162,6 +162,7 @@ static void mod_close(mod_Data * data) static int mod_decode(struct decoder * decoder, char *path) { mod_Data *data; + AudioFormat audio_format; float total_time = 0.0; int ret; float secPerByte; @@ -176,16 +177,16 @@ static int mod_decode(struct decoder * decoder, char *path) } dc.totalTime = 0; - dc.audioFormat.bits = 16; - dc.audioFormat.sampleRate = 44100; - dc.audioFormat.channels = 2; - getOutputAudioFormat(&(dc.audioFormat), &(ob.audioFormat)); + audio_format.bits = 16; + audio_format.sampleRate = 44100; + audio_format.channels = 2; secPerByte = - 1.0 / ((dc.audioFormat.bits * dc.audioFormat.channels / 8.0) * - (float)dc.audioFormat.sampleRate); + 1.0 / ((audio_format.bits * audio_format.channels / 8.0) * + (float)audio_format.sampleRate); + + decoder_initialized(decoder, &audio_format); - decoder_initialized(decoder); while (1) { if (dc.command == DECODE_COMMAND_SEEK) { dc.seekError = 1; diff --git a/src/inputPlugins/mp3_plugin.c b/src/inputPlugins/mp3_plugin.c index 9959f8601..a7f39a3a4 100644 --- a/src/inputPlugins/mp3_plugin.c +++ b/src/inputPlugins/mp3_plugin.c @@ -1021,6 +1021,7 @@ static int mp3_decode(struct decoder * decoder, InputStream * inStream) mp3DecodeData data; MpdTag *tag = NULL; ReplayGainInfo *replayGainInfo = NULL; + AudioFormat audio_format; if (openMp3FromInputStream(inStream, &data, &tag, &replayGainInfo) < 0) { @@ -1032,8 +1033,7 @@ static int mp3_decode(struct decoder * decoder, InputStream * inStream) return 0; } - initAudioFormatFromMp3DecodeData(&data, &(dc.audioFormat)); - getOutputAudioFormat(&(dc.audioFormat), &(ob.audioFormat)); + initAudioFormatFromMp3DecodeData(&data, &audio_format); dc.totalTime = data.totalTime; @@ -1062,7 +1062,7 @@ static int mp3_decode(struct decoder * decoder, InputStream * inStream) freeMpdTag(tag); } - decoder_initialized(decoder); + decoder_initialized(decoder, &audio_format); while (mp3Read(&data, decoder, &replayGainInfo) != DECODE_BREAK) ; /* send last little bit if not DECODE_COMMAND_STOP */ diff --git a/src/inputPlugins/mp4_plugin.c b/src/inputPlugins/mp4_plugin.c index f917ec345..d2c0f1b6c 100644 --- a/src/inputPlugins/mp4_plugin.c +++ b/src/inputPlugins/mp4_plugin.c @@ -88,6 +88,7 @@ static int mp4_decode(struct decoder * mpd_decoder, InputStream * inStream) faacDecHandle decoder; faacDecFrameInfo frameInfo; faacDecConfigurationPtr config; + AudioFormat audio_format; unsigned char *mp4Buffer; unsigned int mp4BufferSize; uint32_t sampleRate; @@ -139,7 +140,7 @@ static int mp4_decode(struct decoder * mpd_decoder, InputStream * inStream) #endif faacDecSetConfiguration(decoder, config); - dc.audioFormat.bits = 16; + audio_format.bits = 16; mp4Buffer = NULL; mp4BufferSize = 0; @@ -154,8 +155,8 @@ static int mp4_decode(struct decoder * mpd_decoder, InputStream * inStream) return -1; } - dc.audioFormat.sampleRate = sampleRate; - dc.audioFormat.channels = channels; + audio_format.sampleRate = sampleRate; + audio_format.channels = channels; file_time = mp4ff_get_track_duration_use_offsets(mp4fh, track); scale = mp4ff_time_scale(mp4fh, track); @@ -245,11 +246,9 @@ static int mp4_decode(struct decoder * mpd_decoder, InputStream * inStream) #ifdef HAVE_FAACDECFRAMEINFO_SAMPLERATE scale = frameInfo.samplerate; #endif - dc.audioFormat.sampleRate = scale; - dc.audioFormat.channels = frameInfo.channels; - getOutputAudioFormat(&(dc.audioFormat), - &(ob.audioFormat)); - decoder_initialized(mpd_decoder); + audio_format.sampleRate = scale; + audio_format.channels = frameInfo.channels; + decoder_initialized(mpd_decoder, &audio_format); } if (channels * (unsigned long)(dur + offset) > frameInfo.samples) { diff --git a/src/inputPlugins/mpc_plugin.c b/src/inputPlugins/mpc_plugin.c index 13f2adcee..5a57ea4c8 100644 --- a/src/inputPlugins/mpc_plugin.c +++ b/src/inputPlugins/mpc_plugin.c @@ -111,6 +111,7 @@ static int mpc_decode(struct decoder * mpd_decoder, InputStream * inStream) mpc_decoder decoder; mpc_reader reader; mpc_streaminfo info; + AudioFormat audio_format; MpcCallbackData data; @@ -161,11 +162,9 @@ static int mpc_decode(struct decoder * mpd_decoder, InputStream * inStream) dc.totalTime = mpc_streaminfo_get_length(&info); - dc.audioFormat.bits = 16; - dc.audioFormat.channels = info.channels; - dc.audioFormat.sampleRate = info.sample_freq; - - getOutputAudioFormat(&(dc.audioFormat), &(ob.audioFormat)); + audio_format.bits = 16; + audio_format.channels = info.channels; + audio_format.sampleRate = info.sample_freq; replayGainInfo = newReplayGainInfo(); replayGainInfo->albumGain = info.gain_album * 0.01; @@ -173,11 +172,11 @@ static int mpc_decode(struct decoder * mpd_decoder, InputStream * inStream) replayGainInfo->trackGain = info.gain_title * 0.01; replayGainInfo->trackPeak = info.peak_title / 32767.0; - decoder_initialized(mpd_decoder); + decoder_initialized(mpd_decoder, &audio_format); while (!eof) { if (dc.command == DECODE_COMMAND_SEEK) { - samplePos = dc.seekWhere * dc.audioFormat.sampleRate; + samplePos = dc.seekWhere * audio_format.sampleRate; if (mpc_decoder_seek_sample(&decoder, samplePos)) { decoder_clear(mpd_decoder); s16 = (mpd_sint16 *) chunk; @@ -210,10 +209,10 @@ static int mpc_decode(struct decoder * mpd_decoder, InputStream * inStream) if (chunkpos >= MPC_CHUNK_SIZE) { total_time = ((float)samplePos) / - dc.audioFormat.sampleRate; + audio_format.sampleRate; bitRate = vbrUpdateBits * - dc.audioFormat.sampleRate / 1152 / 1000; + audio_format.sampleRate / 1152 / 1000; decoder_data(mpd_decoder, inStream, inStream->seekable, @@ -232,10 +231,10 @@ static int mpc_decode(struct decoder * mpd_decoder, InputStream * inStream) } if (dc.command != DECODE_COMMAND_STOP && chunkpos > 0) { - total_time = ((float)samplePos) / dc.audioFormat.sampleRate; + total_time = ((float)samplePos) / audio_format.sampleRate; bitRate = - vbrUpdateBits * dc.audioFormat.sampleRate / 1152 / 1000; + vbrUpdateBits * audio_format.sampleRate / 1152 / 1000; decoder_data(mpd_decoder, NULL, inStream->seekable, chunk, chunkpos, total_time, bitRate, diff --git a/src/inputPlugins/oggflac_plugin.c b/src/inputPlugins/oggflac_plugin.c index b5e73e455..5879b7054 100644 --- a/src/inputPlugins/oggflac_plugin.c +++ b/src/inputPlugins/oggflac_plugin.c @@ -188,7 +188,7 @@ static FLAC__StreamDecoderWriteStatus oggflacWrite(const c_chan++) { u16 = buf[c_chan][c_samp]; uc = (unsigned char *)&u16; - for (i = 0; i < (dc.audioFormat.bits / 8); i++) { + for (i = 0; i < (data->audio_format.bits / 8); i++) { if (data->chunk_length >= FLAC_CHUNK_SIZE) { if (flacSendChunk(data) < 0) { return @@ -345,7 +345,7 @@ static int oggflac_decode(struct decoder * mpd_decoder, InputStream * inStream) goto fail; } - decoder_initialized(mpd_decoder); + decoder_initialized(mpd_decoder, &data.audio_format); while (1) { OggFLAC__seekable_stream_decoder_process_single(decoder); @@ -353,14 +353,14 @@ static int oggflac_decode(struct decoder * mpd_decoder, InputStream * inStream) OggFLAC__SEEKABLE_STREAM_DECODER_OK) { break; } - if (dc.command == DECODE_COMMAND_SEEK) { - FLAC__uint64 sampleToSeek = dc.seekWhere * - dc.audioFormat.sampleRate + 0.5; + if (dc->command == DECODE_COMMAND_SEEK) { + FLAC__uint64 sampleToSeek = dc->seekWhere * + data.audio_format.sampleRate + 0.5; if (OggFLAC__seekable_stream_decoder_seek_absolute (decoder, sampleToSeek)) { decoder_clear(mpd_decoder); data.time = ((float)sampleToSeek) / - dc.audioFormat.sampleRate; + data.audio_format.sampleRate; data.position = 0; } else dc.seekError = 1; diff --git a/src/inputPlugins/oggvorbis_plugin.c b/src/inputPlugins/oggvorbis_plugin.c index 9e1cf89c0..2fb0a9e52 100644 --- a/src/inputPlugins/oggvorbis_plugin.c +++ b/src/inputPlugins/oggvorbis_plugin.c @@ -215,6 +215,7 @@ static int oggvorbis_decode(struct decoder * decoder, InputStream * inStream) OggVorbis_File vf; ov_callbacks callbacks; OggCallbackData data; + AudioFormat audio_format; int current_section; int prev_section = -1; long ret; @@ -264,7 +265,7 @@ static int oggvorbis_decode(struct decoder * decoder, InputStream * inStream) dc.totalTime = ov_time_total(&vf, -1); if (dc.totalTime < 0) dc.totalTime = 0; - dc.audioFormat.bits = 16; + audio_format.bits = 16; while (1) { if (dc.command == DECODE_COMMAND_SEEK) { @@ -281,12 +282,10 @@ static int oggvorbis_decode(struct decoder * decoder, InputStream * inStream) if (current_section != prev_section) { /*printf("new song!\n"); */ vorbis_info *vi = ov_info(&vf, -1); - dc.audioFormat.channels = vi->channels; - dc.audioFormat.sampleRate = vi->rate; + audio_format.channels = vi->channels; + audio_format.sampleRate = vi->rate; if (dc.state == DECODE_STATE_START) { - getOutputAudioFormat(&(dc.audioFormat), - &(ob.audioFormat)); - decoder_initialized(decoder); + decoder_initialized(decoder, &audio_format); } comments = ov_comment(&vf, -1)->user_comments; putOggCommentsIntoOutputBuffer(inStream->metaName, @@ -312,7 +311,7 @@ static int oggvorbis_decode(struct decoder * decoder, InputStream * inStream) decoder_data(decoder, inStream, inStream->seekable, chunk, chunkpos, - ov_pcm_tell(&vf) / dc.audioFormat.sampleRate, + ov_pcm_tell(&vf) / audio_format.sampleRate, bitRate, replayGainInfo); chunkpos = 0; if (dc.command == DECODE_COMMAND_STOP) diff --git a/src/inputPlugins/wavpack_plugin.c b/src/inputPlugins/wavpack_plugin.c index 9d125f053..167278f01 100644 --- a/src/inputPlugins/wavpack_plugin.c +++ b/src/inputPlugins/wavpack_plugin.c @@ -128,6 +128,7 @@ static void wavpack_decode(struct decoder * decoder, WavpackContext *wpc, int canseek, ReplayGainInfo *replayGainInfo) { + AudioFormat audio_format; void (*format_samples)(int Bps, void *buffer, uint32_t samcnt); char chunk[CHUNK_SIZE]; float file_time; @@ -136,12 +137,12 @@ static void wavpack_decode(struct decoder * decoder, int position, outsamplesize; int Bps; - dc.audioFormat.sampleRate = WavpackGetSampleRate(wpc); - dc.audioFormat.channels = WavpackGetReducedChannels(wpc); - dc.audioFormat.bits = WavpackGetBitsPerSample(wpc); + audio_format.sampleRate = WavpackGetSampleRate(wpc); + audio_format.channels = WavpackGetReducedChannels(wpc); + audio_format.bits = WavpackGetBitsPerSample(wpc); - if (dc.audioFormat.bits > 16) - dc.audioFormat.bits = 16; + if (audio_format.bits > 16) + audio_format.bits = 16; if ((WavpackGetMode(wpc) & MODE_FLOAT) == MODE_FLOAT) format_samples = format_samples_float; @@ -159,16 +160,14 @@ static void wavpack_decode(struct decoder * decoder, outsamplesize = Bps; if (outsamplesize > 2) outsamplesize = 2; - outsamplesize *= dc.audioFormat.channels; + outsamplesize *= audio_format.channels; - samplesreq = sizeof(chunk) / (4 * dc.audioFormat.channels); + samplesreq = sizeof(chunk) / (4 * audio_format.channels); - getOutputAudioFormat(&(dc.audioFormat), &(ob.audioFormat)); - - dc.totalTime = (float)allsamples / dc.audioFormat.sampleRate; + dc.totalTime = (float)allsamples / audio_format.sampleRate; dc.seekable = canseek; - decoder_initialized(decoder); + decoder_initialized(decoder, &audio_format); position = 0; @@ -180,7 +179,7 @@ static void wavpack_decode(struct decoder * decoder, decoder_clear(decoder); where = dc.seekWhere * - dc.audioFormat.sampleRate; + audio_format.sampleRate; if (WavpackSeekSample(wpc, where)) position = where; else @@ -202,10 +201,10 @@ static void wavpack_decode(struct decoder * decoder, 1000 + 0.5); position += samplesgot; file_time = (float)position / - dc.audioFormat.sampleRate; + audio_format.sampleRate; format_samples(Bps, chunk, - samplesgot * dc.audioFormat.channels); + samplesgot * audio_format.channels); decoder_data(decoder, NULL, 0, chunk, samplesgot * outsamplesize, -- cgit v1.2.3