static int _rg_write_meta (DB_playItem_t *track) { const char *path = NULL; const char *decoder_id = NULL; deadbeef->pl_lock (); path = strdupa (deadbeef->pl_find_meta_raw (track, ":URI")); int is_subtrack = deadbeef->pl_get_item_flags (track) & DDB_IS_SUBTRACK; if (is_subtrack) { trace ("rg_scanner: Can't write to subtrack of file: %s\n", path); deadbeef->pl_unlock (); return -1; } decoder_id = deadbeef->pl_find_meta_raw (track, ":DECODER"); if (!decoder_id) { trace ("rg_scanner: Invalid decoder in track %s\n", path); deadbeef->pl_unlock (); return -1; } decoder_id = strdupa (decoder_id); int match = track && decoder_id; deadbeef->pl_unlock (); if (match) { int is_subtrack = deadbeef->pl_get_item_flags (track) & DDB_IS_SUBTRACK; if (is_subtrack) { return 0; // only write tags for actual tracks } // find decoder DB_decoder_t *dec = NULL; DB_decoder_t **decoders = deadbeef->plug_get_decoder_list (); for (int i = 0; decoders[i]; i++) { if (!strcmp (decoders[i]->plugin.id, decoder_id)) { dec = decoders[i]; if (dec->write_metadata) { if (dec->write_metadata (track)) { trace ("rg_scanner: Failed to write tag to %s\n", path); return -1; } } else { trace ("rg_scanner: Writing tags is not supported for the file %s\n", path); } break; } } } else { trace ("rg_scanner: Could not find matching decoder for %s\n", path); return -1; } return 0; }
// prepare to decode the track, fill in mandatory plugin fields // return -1 on failure static int example_init (DB_fileinfo_t *_info, DB_playItem_t *it) { example_info_t *info = (example_info_t *)_info; // take this parameters from your input file // we set constants for clarity sake _info->fmt.bps = 16; _info->fmt.channels = 2; _info->fmt.samplerate = 44100; for (int i = 0; i < _info->fmt.channels; i++) { _info->fmt.channelmask |= 1 << i; } _info->readpos = 0; _info->plugin = &plugin; if (it->endsample > 0) { info->startsample = it->startsample; info->endsample = it->endsample; plugin.seek_sample (_info, 0); } else { info->startsample = 0; int TOTALSAMPLES = 1000; // calculate from file info->endsample = TOTALSAMPLES-1; } return 0; }
static void write_meta_worker (void *ctx) { for (int t = 0; t < numtracks; t++) { if (progress_aborted) { break; } DB_playItem_t *track = tracks[t]; deadbeef->pl_lock (); const char *dec = deadbeef->pl_find_meta_raw (track, ":DECODER"); char decoder_id[100]; if (dec) { strncpy (decoder_id, dec, sizeof (decoder_id)); } int match = track && dec; deadbeef->pl_unlock (); if (match) { int is_subtrack = deadbeef->pl_get_item_flags (track) & DDB_IS_SUBTRACK; if (is_subtrack) { continue; } deadbeef->pl_item_ref (track); g_idle_add (set_progress_cb, track); // find decoder DB_decoder_t *dec = NULL; DB_decoder_t **decoders = deadbeef->plug_get_decoder_list (); for (int i = 0; decoders[i]; i++) { if (!strcmp (decoders[i]->plugin.id, decoder_id)) { dec = decoders[i]; if (dec->write_metadata) { dec->write_metadata (track); } break; } } } } g_idle_add (write_finished_cb, ctx); }
static int tta_init (DB_fileinfo_t *_info, DB_playItem_t *it) { tta_info_t *info = (tta_info_t *)_info; deadbeef->pl_lock (); const char *fname = deadbeef->pl_find_meta (it, ":URI") trace ("open_tta_file %s\n", fname); if (open_tta_file (fname, &info->tta, 0) != 0) { deadbeef->pl_unlock (); fprintf (stderr, "tta: failed to open %s\n", fname); return -1; } if (player_init (&info->tta) != 0) { deadbeef->pl_unlock (); fprintf (stderr, "tta: failed to init player for %s\n", fname); return -1; } deadbeef->pl_unlock (); _info->fmt.bps = info->tta.BPS; _info->fmt.channels = info->tta.NCH; _info->fmt.samplerate = info->tta.SAMPLERATE; for (int i = 0; i < _info->fmt.channels; i++) { _info->fmt.channelmask |= 1 << i; } _info->readpos = 0; _info->plugin = &plugin; int64_t endsample = deadbeef->pl_item_get_endsample (it); if (endsample > 0) { info->startsample = deadbeef->pl_item_get_startsample (it); info->endsample = endsample; plugin.seek_sample (_info, 0); } else { info->startsample = 0; info->endsample = (info->tta.DATALENGTH)-1; } trace ("open_tta_file %s success!\n", deadbeef->pl_find_meta (it, ":URI")); return 0; }
static int aac_init (DB_fileinfo_t *_info, DB_playItem_t *it) { aac_info_t *info = (aac_info_t *)_info; info->file = deadbeef->fopen (deadbeef->pl_find_meta (it, ":URI")); if (!info->file) { return -1; } // probe float duration = -1; int samplerate = -1; int channels = -1; int totalsamples = -1; info->junk = deadbeef->junk_get_leading_size (info->file); if (!info->file->vfs->is_streaming ()) { if (info->junk >= 0) { deadbeef->fseek (info->file, info->junk, SEEK_SET); } else { info->junk = 0; } } else { deadbeef->fset_track (info->file, it); } info->mp4track = -1; #if USE_MP4FF info->mp4reader.read = aac_fs_read; info->mp4reader.write = NULL; info->mp4reader.seek = aac_fs_seek; info->mp4reader.truncate = NULL; info->mp4reader.user_data = info; #else info->mp4reader.open = aac_fs_open; info->mp4reader.seek = aac_fs_seek; info->mp4reader.read = aac_fs_read; info->mp4reader.write = NULL; info->mp4reader.close = aac_fs_close; #endif if (!info->file->vfs->is_streaming ()) { #ifdef USE_MP4FF trace ("aac_init: mp4ff_open_read %s\n", deadbeef->pl_find_meta (it, ":URI")); info->mp4file = mp4ff_open_read (&info->mp4reader); if (info->mp4file) { int ntracks = mp4ff_total_tracks (info->mp4file); if (ntracks > 0) { trace ("m4a container detected, ntracks=%d\n", ntracks); int i = -1; unsigned char* buff = 0; unsigned int buff_size = 0; for (i = 0; i < ntracks; i++) { mp4AudioSpecificConfig mp4ASC; mp4ff_get_decoder_config (info->mp4file, i, &buff, &buff_size); if(buff){ int rc = AudioSpecificConfig(buff, buff_size, &mp4ASC); if(rc < 0) continue; break; } } trace ("mp4 probe-buffer size: %d\n", buff_size); if (i != ntracks && buff) { trace ("mp4 track: %d\n", i); int samples = mp4ff_num_samples(info->mp4file, i); info->mp4samples = samples; info->mp4track = i; // init mp4 decoding info->dec = NeAACDecOpen (); unsigned long srate; unsigned char ch; if (NeAACDecInit2(info->dec, buff, buff_size, &srate, &ch) < 0) { trace ("NeAACDecInit2 returned error\n"); free (buff); return -1; } samplerate = srate; channels = ch; samples = (int64_t)samples * srate / mp4ff_time_scale (info->mp4file, i); totalsamples = samples; NeAACDecConfigurationPtr conf = NeAACDecGetCurrentConfiguration (info->dec); conf->dontUpSampleImplicitSBR = 1; NeAACDecSetConfiguration (info->dec, conf); mp4AudioSpecificConfig mp4ASC; if (NeAACDecAudioSpecificConfig(buff, buff_size, &mp4ASC) >= 0) { info->mp4framesize = 1024; if (mp4ASC.frameLengthFlag == 1) { info->mp4framesize = 960; } // if (mp4ASC.sbr_present_flag == 1) { // info->mp4framesize *= 2; // } } totalsamples *= info->mp4framesize; duration = (float)totalsamples / samplerate; } else { mp4ff_close (info->mp4file); info->mp4file = NULL; } if (buff) { free (buff); } } else { mp4ff_close (info->mp4file); info->mp4file = NULL; } } // {{{ libmp4v2 code #else trace ("aac_init: MP4ReadProvider %s\n", deadbeef->pl_find_meta (it, ":URI")); info->mp4file = MP4ReadProvider (deadbeef->pl_find_meta (it, ":URI"), 0, &info->mp4reader); info->mp4track = MP4FindTrackId(info->mp4file, 0, "audio", 0); trace ("aac_init: MP4FindTrackId returned %d\n", info->mp4track); if (info->mp4track >= 0) { info->timescale = MP4GetTrackTimeScale(info->mp4file, info->mp4track); u_int8_t* pConfig; uint32_t configSize = 0; bool res = MP4GetTrackESConfiguration(info->mp4file, info->mp4track, &pConfig, &configSize); mp4AudioSpecificConfig mp4ASC; int rc = AudioSpecificConfig(pConfig, configSize, &mp4ASC); if (rc >= 0) { _info->samplerate = mp4ASC.samplingFrequency; _info->channels = MP4GetTrackAudioChannels (info->mp4file, info->mp4track); totalsamples = MP4GetTrackNumberOfSamples (info->mp4file, info->mp4track) * 1024 * _info->channels; // init mp4 decoding info->dec = NeAACDecOpen (); unsigned long srate; unsigned char ch; if (NeAACDecInit2(info->dec, pConfig, configSize, &srate, &ch) < 0) { trace ("NeAACDecInit2 returned error\n"); return -1; } samplerate = srate; channels = ch; NeAACDecConfigurationPtr conf = NeAACDecGetCurrentConfiguration (info->dec); conf->dontUpSampleImplicitSBR = 1; NeAACDecSetConfiguration (info->dec, conf); mp4AudioSpecificConfig mp4ASC; if (NeAACDecAudioSpecificConfig(pConfig, configSize, &mp4ASC) >= 0) { info->mp4framesize = 1024; if (mp4ASC.frameLengthFlag == 1) { info->mp4framesize = 960; } // if (mp4ASC.sbr_present_flag == 1) { // info->mp4framesize *= 2; // } } //totalsamples *= info->mp4framesize; free (pConfig); info->maxSampleSize = MP4GetTrackMaxSampleSize(info->mp4file, info->mp4track); info->samplebuffer = malloc (info->maxSampleSize); info->mp4sample = 1; } else { MP4Close (info->mp4file); info->mp4file = NULL; } } else { MP4Close (info->mp4file); info->mp4file = NULL; } #endif // }}} if (!info->mp4file) { trace ("mp4 track not found, looking for aac stream...\n"); if (info->junk >= 0) { deadbeef->fseek (info->file, info->junk, SEEK_SET); } else { deadbeef->rewind (info->file); } int offs = parse_aac_stream (info->file, &samplerate, &channels, &duration, &totalsamples); if (offs == -1) { trace ("aac stream not found\n"); return -1; } if (offs > info->junk) { info->junk = offs; } if (info->junk >= 0) { deadbeef->fseek (info->file, info->junk, SEEK_SET); } else { deadbeef->rewind (info->file); } trace ("found aac stream (junk: %d, offs: %d)\n", info->junk, offs); } _info->fmt.channels = channels; _info->fmt.samplerate = samplerate; } else { // sync before attempting to init int samplerate, channels; float duration; int offs = parse_aac_stream (info->file, &samplerate, &channels, &duration, NULL); if (offs < 0) { trace ("aac: parse_aac_stream failed\n"); return -1; } if (offs > info->junk) { info->junk = offs; } trace("parse_aac_stream returned %x\n", offs); deadbeef->pl_replace_meta (it, "!FILETYPE", "AAC"); } // duration = (float)totalsamples / samplerate; // deadbeef->pl_set_item_duration (it, duration); _info->fmt.bps = 16; _info->plugin = &plugin; if (!info->mp4file) { //trace ("NeAACDecGetCapabilities\n"); //unsigned long caps = NeAACDecGetCapabilities(); trace ("NeAACDecOpen\n"); info->dec = NeAACDecOpen (); trace ("prepare for NeAACDecInit: fread %d from offs %lld\n", AAC_BUFFER_SIZE, deadbeef->ftell (info->file)); info->remaining = deadbeef->fread (info->buffer, 1, AAC_BUFFER_SIZE, info->file); NeAACDecConfigurationPtr conf = NeAACDecGetCurrentConfiguration (info->dec); // conf->dontUpSampleImplicitSBR = 1; NeAACDecSetConfiguration (info->dec, conf); unsigned long srate; unsigned char ch; trace ("NeAACDecInit (%d bytes)\n", info->remaining); int consumed = NeAACDecInit (info->dec, info->buffer, info->remaining, &srate, &ch); trace ("NeAACDecInit returned samplerate=%d, channels=%d, consumed: %d\n", (int)srate, (int)ch, consumed); if (consumed < 0) { trace ("NeAACDecInit returned %d\n", consumed); return -1; } if (consumed > info->remaining) { trace ("NeAACDecInit consumed more than available! wtf?\n"); return -1; } if (consumed == info->remaining) { info->remaining = 0; } else if (consumed > 0) { memmove (info->buffer, info->buffer + consumed, info->remaining - consumed); info->remaining -= consumed; } _info->fmt.channels = ch; _info->fmt.samplerate = srate; } if (!info->file->vfs->is_streaming ()) { if (it->endsample > 0) { info->startsample = it->startsample; info->endsample = it->endsample; plugin.seek_sample (_info, 0); } else { info->startsample = 0; info->endsample = totalsamples-1; } } trace ("totalsamples: %d, endsample: %d, samples-from-duration: %d\n", totalsamples-1, info->endsample, (int)deadbeef->pl_get_item_duration (it)*44100); for (int i = 0; i < _info->fmt.channels; i++) { _info->fmt.channelmask |= 1 << i; } info->noremap = 0; info->remap[0] = -1; trace ("init success\n"); return 0; }
static int wv_init (DB_fileinfo_t *_info, DB_playItem_t *it) { wvctx_t *info = (wvctx_t *)_info; deadbeef->pl_lock (); info->file = deadbeef->fopen (deadbeef->pl_find_meta (it, ":URI")); deadbeef->pl_unlock (); if (!info->file) { return -1; } #ifndef TINYWV deadbeef->pl_lock (); const char *uri = deadbeef->pl_find_meta (it, ":URI"); char *c_fname = alloca (strlen (uri) + 2); if (c_fname) { strcpy (c_fname, uri); strcat (c_fname, "c"); info->c_file = deadbeef->fopen (c_fname); } else { fprintf (stderr, "wavpack warning: failed to alloc memory for correction file name\n"); } deadbeef->pl_unlock (); #endif char error[80]; #ifdef TINYWV info->ctx = WavpackOpenFileInput (wv_read_stream, info->file, error); #else info->ctx = WavpackOpenFileInputEx (&wsr, info->file, info->c_file, error, OPEN_NORMALIZE, 0); #endif if (!info->ctx) { fprintf (stderr, "wavpack error: %s\n", error); return -1; } _info->plugin = &plugin; _info->fmt.bps = WavpackGetBytesPerSample (info->ctx) * 8; _info->fmt.channels = WavpackGetNumChannels (info->ctx); _info->fmt.samplerate = WavpackGetSampleRate (info->ctx); _info->fmt.is_float = (WavpackGetMode (info->ctx) & MODE_FLOAT) ? 1 : 0; // FIXME: streamer and maybe output plugins need to be fixed to support // arbitrary channelmask // _info->fmt.channelmask = WavpackGetChannelMask (info->ctx); for (int i = 0; i < _info->fmt.channels; i++) { _info->fmt.channelmask |= 1 << i; } _info->readpos = 0; if (it->endsample > 0) { info->startsample = it->startsample; info->endsample = it->endsample; if (plugin.seek_sample (_info, 0) < 0) { return -1; } } else { info->startsample = 0; info->endsample = WavpackGetNumSamples (info->ctx)-1; } return 0; }
static int aac_init (DB_fileinfo_t *_info, DB_playItem_t *it) { aac_info_t *info = (aac_info_t *)_info; deadbeef->pl_lock (); info->file = deadbeef->fopen (deadbeef->pl_find_meta (it, ":URI")); deadbeef->pl_unlock (); if (!info->file) { return -1; } // probe float duration = -1; int samplerate = -1; int channels = -1; int totalsamples = -1; info->junk = deadbeef->junk_get_leading_size (info->file); if (!info->file->vfs->is_streaming ()) { if (info->junk >= 0) { deadbeef->fseek (info->file, info->junk, SEEK_SET); } else { info->junk = 0; } } else { deadbeef->fset_track (info->file, it); } info->mp4track = -1; info->mp4reader.read = aac_fs_read; info->mp4reader.write = NULL; info->mp4reader.seek = aac_fs_seek; info->mp4reader.truncate = NULL; info->mp4reader.user_data = info; if (!info->file->vfs->is_streaming ()) { trace ("aac_init: mp4ff_open_read %s\n", deadbeef->pl_find_meta (it, ":URI")); info->mp4 = mp4ff_open_read (&info->mp4reader); if (info->mp4) { int ntracks = mp4ff_total_tracks (info->mp4); for (int i = 0; i < ntracks; i++) { if (mp4ff_get_track_type (info->mp4, i) != TRACK_AUDIO) { continue; } int res = mp4_track_get_info (info->mp4, i, &duration, &samplerate, &channels, &totalsamples, &info->mp4framesize); if (res >= 0 && duration > 0) { info->mp4track = i; break; } } trace ("track: %d\n", info->mp4track); if (info->mp4track >= 0) { // prepare decoder int res = mp4_track_get_info (info->mp4, info->mp4track, &duration, &samplerate, &channels, &totalsamples, &info->mp4framesize); if (res != 0) { trace ("aac: mp4_track_get_info(%d) returned error\n", info->mp4track); return -1; } // init mp4 decoding info->mp4samples = mp4ff_num_samples(info->mp4, info->mp4track); info->dec = NeAACDecOpen (); unsigned long srate; unsigned char ch; unsigned char* buff = 0; unsigned int buff_size = 0; mp4AudioSpecificConfig mp4ASC; mp4ff_get_decoder_config (info->mp4, info->mp4track, &buff, &buff_size); if (NeAACDecInit2(info->dec, buff, buff_size, &srate, &ch) < 0) { trace ("NeAACDecInit2 returned error\n"); free (buff); return -1; } if (buff) { free (buff); } trace ("aac: successfully initialized track %d\n", info->mp4track); _info->fmt.samplerate = samplerate; _info->fmt.channels = channels; } else { trace ("aac: track not found in mp4 container\n"); mp4ff_close (info->mp4); info->mp4 = NULL; } } if (!info->mp4) { trace ("aac: looking for raw stream...\n"); if (info->junk >= 0) { deadbeef->fseek (info->file, info->junk, SEEK_SET); } else { deadbeef->rewind (info->file); } int offs = parse_aac_stream (info->file, &samplerate, &channels, &duration, &totalsamples); if (offs == -1) { trace ("aac stream not found\n"); return -1; } if (offs > info->junk) { info->junk = offs; } if (info->junk >= 0) { deadbeef->fseek (info->file, info->junk, SEEK_SET); } else { deadbeef->rewind (info->file); } trace ("found aac stream (junk: %d, offs: %d)\n", info->junk, offs); _info->fmt.channels = channels; _info->fmt.samplerate = samplerate; } } else { // sync before attempting to init int samplerate, channels; float duration; int offs = parse_aac_stream (info->file, &samplerate, &channels, &duration, NULL); if (offs < 0) { trace ("aac: parse_aac_stream failed\n"); return -1; } if (offs > info->junk) { info->junk = offs; } trace("parse_aac_stream returned %x\n", offs); deadbeef->pl_replace_meta (it, "!FILETYPE", "AAC"); } // duration = (float)totalsamples / samplerate; // deadbeef->pl_set_item_duration (it, duration); _info->fmt.bps = 16; _info->plugin = &plugin; if (!info->mp4) { trace ("NeAACDecOpen for raw stream\n"); info->dec = NeAACDecOpen (); trace ("prepare for NeAACDecInit: fread %d from offs %lld\n", AAC_BUFFER_SIZE, deadbeef->ftell (info->file)); info->remaining = deadbeef->fread (info->buffer, 1, AAC_BUFFER_SIZE, info->file); NeAACDecConfigurationPtr conf = NeAACDecGetCurrentConfiguration (info->dec); // conf->dontUpSampleImplicitSBR = 1; NeAACDecSetConfiguration (info->dec, conf); unsigned long srate; unsigned char ch; trace ("NeAACDecInit (%d bytes)\n", info->remaining); int consumed = NeAACDecInit (info->dec, info->buffer, info->remaining, &srate, &ch); trace ("NeAACDecInit returned samplerate=%d, channels=%d, consumed: %d\n", (int)srate, (int)ch, consumed); if (consumed < 0) { trace ("NeAACDecInit returned %d\n", consumed); return -1; } if (consumed > info->remaining) { trace ("NeAACDecInit consumed more than available! wtf?\n"); return -1; } if (consumed == info->remaining) { info->remaining = 0; } else if (consumed > 0) { memmove (info->buffer, info->buffer + consumed, info->remaining - consumed); info->remaining -= consumed; } _info->fmt.channels = ch; _info->fmt.samplerate = srate; } if (!info->file->vfs->is_streaming ()) { if (it->endsample > 0) { info->startsample = it->startsample; info->endsample = it->endsample; plugin.seek_sample (_info, 0); } else { info->startsample = 0; info->endsample = totalsamples-1; } } if (_info->fmt.channels == 7) { _info->fmt.channels = 8; } trace ("totalsamples: %d, endsample: %d, samples-from-duration: %d, samplerate %d, channels %d\n", totalsamples-1, info->endsample, (int)deadbeef->pl_get_item_duration (it)*44100, _info->fmt.samplerate, _info->fmt.channels); for (int i = 0; i < _info->fmt.channels; i++) { _info->fmt.channelmask |= 1 << i; } info->noremap = 0; info->remap[0] = -1; trace ("init success\n"); return 0; }
static int sndfile_init (DB_fileinfo_t *_info, DB_playItem_t *it) { sndfile_info_t *info = (sndfile_info_t*)_info; SF_INFO inf; DB_FILE *fp = deadbeef->fopen (deadbeef->pl_find_meta (it, ":URI")); if (!fp) { trace ("sndfile: failed to open %s\n", deadbeef->pl_find_meta (it, ":URI")); return -1; } int fsize = deadbeef->fgetlength (fp); info->file = fp; info->ctx = sf_open_virtual (&vfs, SFM_READ, &inf, info); if (!info->ctx) { trace ("sndfile: %s: unsupported file format\n"); return -1; } _info->plugin = &plugin; info->sf_format = inf.format&0x000f; switch (inf.format&0x000f) { case SF_FORMAT_PCM_S8: case SF_FORMAT_PCM_U8: _info->fmt.bps = 8; break; case SF_FORMAT_PCM_16: _info->fmt.bps = 16; break; case SF_FORMAT_PCM_24: _info->fmt.bps = 24; break; case SF_FORMAT_FLOAT: _info->fmt.is_float = 1; case SF_FORMAT_PCM_32: _info->fmt.bps = 32; break; default: info->read_as_short = 1; _info->fmt.bps = 16; trace ("[sndfile] unidentified input format: 0x%X\n", inf.format&0x000f); break; } _info->fmt.channels = inf.channels; _info->fmt.samplerate = inf.samplerate; int channel_map [inf.channels]; int cmdres = sf_command (info->ctx, SFC_GET_CHANNEL_MAP_INFO, channel_map, sizeof (channel_map)) ; if (cmdres != SF_FALSE) { // channel map found, convert to channel mask _info->fmt.channelmask = wavex_gen_channel_mask (channel_map, inf.channels); } else { // channel map not found, generate from channel number for (int i = 0; i < inf.channels; i++) { _info->fmt.channelmask |= 1 << i; } } _info->readpos = 0; if (it->endsample > 0) { info->startsample = it->startsample; info->endsample = it->endsample; if (plugin.seek_sample (_info, 0) < 0) { return -1; } } else { info->startsample = 0; info->endsample = inf.frames-1; } // hack bitrate int totalsamples = inf.frames; float sec = (float)totalsamples / inf.samplerate; if (sec > 0) { info->bitrate = fsize / sec * 8 / 1000; } else { info->bitrate = -1; } return 0; }
int convert (DB_playItem_t *it, const char *out, int output_bps, int output_is_float, ddb_encoder_preset_t *encoder_preset, ddb_dsp_preset_t *dsp_preset, int *abort) { char *buffer = NULL; char *dspbuffer = NULL; if (deadbeef->pl_get_item_duration (it) <= 0) { deadbeef->pl_lock (); fprintf (stderr, "converter: stream %s doesn't have finite length, skipped\n", deadbeef->pl_find_meta (it, ":URI")); deadbeef->pl_unlock (); return -1; } int err = -1; FILE *enc_pipe = NULL; int temp_file = -1; DB_decoder_t *dec = NULL; DB_fileinfo_t *fileinfo = NULL; char input_file_name[PATH_MAX] = ""; deadbeef->pl_lock (); dec = (DB_decoder_t *)deadbeef->plug_get_for_id (deadbeef->pl_find_meta (it, ":DECODER")); deadbeef->pl_unlock (); if (dec) { fileinfo = dec->open (0); if (fileinfo && dec->init (fileinfo, DB_PLAYITEM (it)) != 0) { deadbeef->pl_lock (); fprintf (stderr, "converter: failed to decode file %s\n", deadbeef->pl_find_meta (it, ":URI")); deadbeef->pl_unlock (); goto error; } if (fileinfo) { if (output_bps == -1) { output_bps = fileinfo->fmt.bps; output_is_float = fileinfo->fmt.is_float; } char *final_path = strdupa (out); char *sep = strrchr (final_path, '/'); if (sep) { *sep = 0; if (!check_dir (final_path, 0755)) { fprintf (stderr, "converter: failed to create output folder: %s\n", final_path); goto error; } } if (encoder_preset->method == DDB_ENCODER_METHOD_FILE) { const char *tmp = getenv ("TMPDIR"); if (!tmp) { tmp = "/tmp"; } snprintf (input_file_name, sizeof (input_file_name), "%s/ddbconvXXXXXX", tmp); char *res = mktemp (input_file_name); strcat (input_file_name, ".wav"); } else { strcpy (input_file_name, "-"); } char enc[2000]; memset (enc, 0, sizeof (enc)); char escaped_out[PATH_MAX]; escape_filepath (out, escaped_out, sizeof (escaped_out)); // formatting: %o = outfile, %i = infile char *e = encoder_preset->encoder; char *o = enc; *o = 0; int len = sizeof (enc); while (e && *e) { if (len <= 0) { fprintf (stderr, "converter: failed to assemble encoder command line - buffer is not big enough, try to shorten your parameters. max allowed length is %u characters\n", (unsigned)sizeof (enc)); goto error; } if (e[0] == '%' && e[1]) { if (e[1] == 'o') { int l = snprintf (o, len, "\"%s\"", escaped_out); o += l; len -= l; } else if (e[1] == 'i') { int l = snprintf (o, len, "\"%s\"", input_file_name); o += l; len -= l; } else { strncpy (o, e, 2); o += 2; len -= 2; } e += 2; } else { *o++ = *e++; *o = 0; len--; } } fprintf (stderr, "converter: will encode using: %s\n", enc[0] ? enc : "internal RIFF WAVE writer"); mode_t wrmode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; if (!encoder_preset->encoder[0]) { // write to wave file trace ("opening %s\n", out); temp_file = open (out, O_LARGEFILE | O_WRONLY | O_CREAT | O_TRUNC, wrmode); if (temp_file == -1) { fprintf (stderr, "converter: failed to open output wave file %s\n", out); goto error; } } else if (encoder_preset->method == DDB_ENCODER_METHOD_FILE) { temp_file = open (input_file_name, O_LARGEFILE | O_WRONLY | O_CREAT | O_TRUNC, wrmode); if (temp_file == -1) { fprintf (stderr, "converter: failed to open temp file %s\n", input_file_name); goto error; } } else { enc_pipe = popen (enc, "w"); if (!enc_pipe) { fprintf (stderr, "converter: failed to open encoder\n"); goto error; } } if (temp_file == -1 && enc_pipe) { temp_file = fileno (enc_pipe); } // write wave header char wavehdr_int[] = { 0x52, 0x49, 0x46, 0x46, 0x00, 0x00, 0x00, 0x00, 0x57, 0x41, 0x56, 0x45, 0x66, 0x6d, 0x74, 0x20, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x44, 0xac, 0x00, 0x00, 0x10, 0xb1, 0x02, 0x00, 0x04, 0x00, 0x10, 0x00, 0x64, 0x61, 0x74, 0x61 }; char wavehdr_float[] = { 0x52, 0x49, 0x46, 0x46, 0x00, 0x00, 0x00, 0x00, 0x57, 0x41, 0x56, 0x45, 0x66, 0x6d, 0x74, 0x20, 0x28, 0x00, 0x00, 0x00, 0xfe, 0xff, 0x02, 0x00, 0x40, 0x1f, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x00, 0x08, 0x00, 0x20, 0x00, 0x16, 0x00, 0x20, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71, 0x66, 0x61, 0x63, 0x74, 0x04, 0x00, 0x00, 0x00, 0xc5, 0x5b, 0x00, 0x00, 0x64, 0x61, 0x74, 0x61 }; char *wavehdr = output_is_float ? wavehdr_float : wavehdr_int; int wavehdr_size = output_is_float ? sizeof (wavehdr_float) : sizeof (wavehdr_int); int header_written = 0; uint32_t outsize = 0; uint32_t outsr = fileinfo->fmt.samplerate; uint16_t outch = fileinfo->fmt.channels; int samplesize = fileinfo->fmt.channels * fileinfo->fmt.bps / 8; // block size int bs = 2000 * samplesize; // expected buffer size after worst-case dsp int dspsize = bs/samplesize*sizeof(float)*8*48; buffer = malloc (dspsize); // account for up to float32 7.1 resampled to 48x ratio dspbuffer = malloc (dspsize); int eof = 0; for (;;) { if (eof) { break; } if (abort && *abort) { break; } int sz = dec->read (fileinfo, buffer, bs); if (sz != bs) { eof = 1; } if (dsp_preset) { ddb_waveformat_t fmt; ddb_waveformat_t outfmt; memcpy (&fmt, &fileinfo->fmt, sizeof (fmt)); memcpy (&outfmt, &fileinfo->fmt, sizeof (fmt)); fmt.bps = 32; fmt.is_float = 1; deadbeef->pcm_convert (&fileinfo->fmt, buffer, &fmt, dspbuffer, sz); ddb_dsp_context_t *dsp = dsp_preset->chain; int frames = sz / samplesize; while (dsp) { frames = dsp->plugin->process (dsp, (float *)dspbuffer, frames, dspsize / (fmt.channels * 4), &fmt, NULL); if (frames <= 0) { break; } dsp = dsp->next; } if (frames <= 0) { fprintf (stderr, "converter: dsp error, please check you dsp preset\n"); goto error; } outsr = fmt.samplerate; outch = fmt.channels; outfmt.bps = output_bps; outfmt.is_float = output_is_float; outfmt.channels = outch; outfmt.samplerate = outsr; int n = deadbeef->pcm_convert (&fmt, dspbuffer, &outfmt, buffer, frames * sizeof (float) * fmt.channels); sz = n; } else if (fileinfo->fmt.bps != output_bps || fileinfo->fmt.is_float != output_is_float) { ddb_waveformat_t outfmt; memcpy (&outfmt, &fileinfo->fmt, sizeof (outfmt)); outfmt.bps = output_bps; outfmt.is_float = output_is_float; outfmt.channels = outch; outfmt.samplerate = outsr; int frames = sz / samplesize; int n = deadbeef->pcm_convert (&fileinfo->fmt, buffer, &outfmt, dspbuffer, frames * samplesize); memcpy (buffer, dspbuffer, n); sz = n; } outsize += sz; if (!header_written) { uint64_t size = (int64_t)(it->endsample-it->startsample) * outch * output_bps / 8; if (!size) { size = (double)deadbeef->pl_get_item_duration (it) * fileinfo->fmt.samplerate * outch * output_bps / 8; } if (outsr != fileinfo->fmt.samplerate) { uint64_t temp = size; temp *= outsr; temp /= fileinfo->fmt.samplerate; size = temp; } uint64_t chunksize; chunksize = size + 40; // for float, add 36 more if (output_is_float) { chunksize += 36; } uint32_t size32 = 0xffffffff; if (chunksize <= 0xffffffff) { size32 = chunksize; } memcpy (&wavehdr[4], &size32, 4); memcpy (&wavehdr[22], &outch, 2); memcpy (&wavehdr[24], &outsr, 4); uint16_t blockalign = outch * output_bps / 8; memcpy (&wavehdr[32], &blockalign, 2); memcpy (&wavehdr[34], &output_bps, 2); size32 = 0xffffffff; if (size <= 0xffffffff) { size32 = size; } if (wavehdr_size != write (temp_file, wavehdr, wavehdr_size)) { fprintf (stderr, "converter: wave header write error\n"); goto error; } if (encoder_preset->method == DDB_ENCODER_METHOD_PIPE) { size32 = 0; } if (write (temp_file, &size32, sizeof (size32)) != sizeof (size32)) { fprintf (stderr, "converter: wave header size write error\n"); goto error; } header_written = 1; } int64_t res = write (temp_file, buffer, sz); if (sz != res) { fprintf (stderr, "converter: write error (%"PRId64" bytes written out of %d)\n", res, sz); goto error; } } if (abort && *abort) { goto error; } if (temp_file != -1 && (!enc_pipe || temp_file != fileno (enc_pipe))) { lseek (temp_file, wavehdr_size, SEEK_SET); if (4 != write (temp_file, &outsize, 4)) { fprintf (stderr, "converter: data size write error\n"); goto error; } if (temp_file != -1 && (!enc_pipe || temp_file != fileno (enc_pipe))) { close (temp_file); temp_file = -1; } } if (encoder_preset->encoder[0] && encoder_preset->method == DDB_ENCODER_METHOD_FILE) { enc_pipe = popen (enc, "w"); } } } err = 0; error: if (buffer) { free (buffer); buffer = NULL; } if (dspbuffer) { free (dspbuffer); dspbuffer = NULL; } if (temp_file != -1 && (!enc_pipe || temp_file != fileno (enc_pipe))) { close (temp_file); temp_file = -1; } if (enc_pipe) { pclose (enc_pipe); enc_pipe = NULL; } if (dec && fileinfo) { dec->free (fileinfo); fileinfo = NULL; } if (abort && *abort && out[0]) { unlink (out); } if (input_file_name[0] && strcmp (input_file_name, "-")) { unlink (input_file_name); } if (err != 0) { return err; } // write junklib tags DB_playItem_t *out_it = NULL; if (encoder_preset->tag_id3v2 || encoder_preset->tag_id3v1 || encoder_preset->tag_apev2 || encoder_preset->tag_flac || encoder_preset->tag_oggvorbis) { out_it = deadbeef->pl_item_alloc (); deadbeef->pl_item_copy (out_it, it); deadbeef->pl_set_item_flags (out_it, 0); DB_metaInfo_t *m = deadbeef->pl_get_metadata_head (out_it); while (m) { DB_metaInfo_t *next = m->next; if (m->key[0] == ':' || m->key[0] == '!' || !strcasecmp (m->key, "cuesheet")) { deadbeef->pl_delete_metadata (out_it, m); } m = next; } deadbeef->pl_replace_meta (out_it, ":URI", out); } uint32_t tagflags = 0; if (encoder_preset->tag_id3v2) { tagflags |= JUNK_WRITE_ID3V2; } if (encoder_preset->tag_id3v1) { tagflags |= JUNK_WRITE_ID3V1; } if (encoder_preset->tag_apev2) { tagflags |= JUNK_WRITE_APEV2; } if (tagflags) { tagflags |= JUNK_STRIP_ID3V2 | JUNK_STRIP_APEV2 | JUNK_STRIP_ID3V1; deadbeef->junk_rewrite_tags (out_it, tagflags, encoder_preset->id3v2_version + 3, "iso8859-1"); } // write flac tags if (encoder_preset->tag_flac) { // find flac decoder plugin DB_decoder_t **plugs = deadbeef->plug_get_decoder_list (); DB_decoder_t *flac = NULL; for (int i = 0; plugs[i]; i++) { if (!strcmp (plugs[i]->plugin.id, "stdflac")) { flac = plugs[i]; break; } } if (!flac) { fprintf (stderr, "converter: flac plugin not found, cannot write flac metadata\n"); } else { if (0 != flac->write_metadata (out_it)) { fprintf (stderr, "converter: failed to write flac metadata, not a flac file?\n"); } } } // write vorbis tags if (encoder_preset->tag_oggvorbis) { // find flac decoder plugin DB_decoder_t **plugs = deadbeef->plug_get_decoder_list (); int res = -1; for (int i = 0; plugs[i]; i++) { if (!strcmp (plugs[i]->plugin.id, "stdogg") || !strcmp (plugs[i]->plugin.id, "stdopus")) { res = plugs[i]->write_metadata (out_it); if (!res) { break; } } } if (res) { fprintf (stderr, "converter: failed to write ogg metadata, not an ogg file?\n"); } } if (out_it) { deadbeef->pl_item_unref (out_it); } return err; }
int convert (DB_playItem_t *it, const char *outfolder, const char *outfile, int output_bps, int output_is_float, int preserve_folder_structure, const char *root_folder, ddb_encoder_preset_t *encoder_preset, ddb_dsp_preset_t *dsp_preset, int *abort) { if (deadbeef->pl_get_item_duration (it) <= 0) { deadbeef->pl_lock (); const char *fname = deadbeef->pl_find_meta (it, ":URI"); fprintf (stderr, "converter: stream %s doesn't have finite length, skipped\n", fname); deadbeef->pl_unlock (); return -1; } char *path = outfolder[0] ? strdupa (outfolder) : strdupa (getenv("HOME")); if (!check_dir (path, 0755)) { fprintf (stderr, "converter: failed to create output folder: %s\n", outfolder); return -1; } int err = -1; FILE *enc_pipe = NULL; FILE *temp_file = NULL; DB_decoder_t *dec = NULL; DB_fileinfo_t *fileinfo = NULL; char out[PATH_MAX] = ""; // full path to output file char input_file_name[PATH_MAX] = ""; dec = (DB_decoder_t *)deadbeef->plug_get_for_id (deadbeef->pl_find_meta (it, ":DECODER")); if (dec) { fileinfo = dec->open (0); if (fileinfo && dec->init (fileinfo, DB_PLAYITEM (it)) != 0) { deadbeef->pl_lock (); fprintf (stderr, "converter: failed to decode file %s\n", deadbeef->pl_find_meta (it, ":URI")); deadbeef->pl_unlock (); goto error; } if (fileinfo) { if (output_bps == -1) { output_bps = fileinfo->fmt.bps; output_is_float = fileinfo->fmt.is_float; } get_output_path (it, outfolder, outfile, encoder_preset, out, sizeof (out)); if (encoder_preset->method == DDB_ENCODER_METHOD_FILE) { const char *tmp = getenv ("TMPDIR"); if (!tmp) { tmp = "/tmp"; } snprintf (input_file_name, sizeof (input_file_name), "%s/ddbconvXXXXXX", tmp); mktemp (input_file_name); strcat (input_file_name, ".wav"); } else { strcpy (input_file_name, "-"); } char enc[2000]; memset (enc, 0, sizeof (enc)); // formatting: %o = outfile, %i = infile char *e = encoder_preset->encoder; char *o = enc; *o = 0; int len = sizeof (enc); while (e && *e) { if (len <= 0) { fprintf (stderr, "converter: failed to assemble encoder command line - buffer is not big enough, try to shorten your parameters. max allowed length is %u characters\n", (unsigned)sizeof (enc)); goto error; } if (e[0] == '%' && e[1]) { if (e[1] == 'o') { int l = snprintf (o, len, "\"%s\"", out); o += l; len -= l; } else if (e[1] == 'i') { int l = snprintf (o, len, "\"%s\"", input_file_name); o += l; len -= l; } else { strncpy (o, e, 2); o += 2; len -= 2; } e += 2; } else { *o++ = *e++; *o = 0; len--; } } fprintf (stderr, "converter: will encode using: %s\n", enc[0] ? enc : "internal RIFF WAVE writer"); if (!encoder_preset->encoder[0]) { // write to wave file temp_file = fopen (out, "w+b"); if (!temp_file) { fprintf (stderr, "converter: failed to open output wave file %s\n", out); goto error; } } else if (encoder_preset->method == DDB_ENCODER_METHOD_FILE) { temp_file = fopen (input_file_name, "w+b"); if (!temp_file) { fprintf (stderr, "converter: failed to open temp file %s\n", input_file_name); goto error; } } else { enc_pipe = popen (enc, "w"); if (!enc_pipe) { fprintf (stderr, "converter: failed to open encoder\n"); goto error; } } if (!temp_file) { temp_file = enc_pipe; } // write wave header char wavehdr_int[] = { 0x52, 0x49, 0x46, 0x46, 0x24, 0x70, 0x0d, 0x00, 0x57, 0x41, 0x56, 0x45, 0x66, 0x6d, 0x74, 0x20, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x44, 0xac, 0x00, 0x00, 0x10, 0xb1, 0x02, 0x00, 0x04, 0x00, 0x10, 0x00, 0x64, 0x61, 0x74, 0x61 }; char wavehdr_float[] = { 0x52, 0x49, 0x46, 0x46, 0x2a, 0xdf, 0x02, 0x00, 0x57, 0x41, 0x56, 0x45, 0x66, 0x6d, 0x74, 0x20, 0x28, 0x00, 0x00, 0x00, 0xfe, 0xff, 0x02, 0x00, 0x40, 0x1f, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x00, 0x08, 0x00, 0x20, 0x00, 0x16, 0x00, 0x20, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71, 0x66, 0x61, 0x63, 0x74, 0x04, 0x00, 0x00, 0x00, 0xc5, 0x5b, 0x00, 0x00, 0x64, 0x61, 0x74, 0x61 }; char *wavehdr = output_is_float ? wavehdr_float : wavehdr_int; int wavehdr_size = output_is_float ? sizeof (wavehdr_float) : sizeof (wavehdr_int); int header_written = 0; uint32_t outsize = 0; uint32_t outsr = fileinfo->fmt.samplerate; uint16_t outch = fileinfo->fmt.channels; int samplesize = fileinfo->fmt.channels * fileinfo->fmt.bps / 8; int bs = 10250 * samplesize; char buffer[bs * 4]; int dspsize = bs / samplesize * sizeof (float) * fileinfo->fmt.channels; char dspbuffer[dspsize * 4]; int eof = 0; for (;;) { if (eof) { break; } if (abort && *abort) { break; } int sz = dec->read (fileinfo, buffer, bs); if (sz != bs) { eof = 1; } if (dsp_preset) { ddb_waveformat_t fmt; ddb_waveformat_t outfmt; memcpy (&fmt, &fileinfo->fmt, sizeof (fmt)); memcpy (&outfmt, &fileinfo->fmt, sizeof (fmt)); fmt.bps = 32; fmt.is_float = 1; deadbeef->pcm_convert (&fileinfo->fmt, buffer, &fmt, dspbuffer, sz); ddb_dsp_context_t *dsp = dsp_preset->chain; int frames = sz / samplesize; while (dsp) { frames = dsp->plugin->process (dsp, (float *)dspbuffer, frames, sizeof (dspbuffer) / (fmt.channels * 4), &fmt, NULL); dsp = dsp->next; } outsr = fmt.samplerate; outch = fmt.channels; outfmt.bps = output_bps; outfmt.is_float = output_is_float; outfmt.channels = outch; outfmt.samplerate = outsr; int n = deadbeef->pcm_convert (&fmt, dspbuffer, &outfmt, buffer, frames * sizeof (float) * fmt.channels); sz = n; } else if (fileinfo->fmt.bps != output_bps || fileinfo->fmt.is_float != output_is_float) { ddb_waveformat_t outfmt; memcpy (&outfmt, &fileinfo->fmt, sizeof (outfmt)); outfmt.bps = output_bps; outfmt.is_float = output_is_float; outfmt.channels = outch; outfmt.samplerate = outsr; int frames = sz / samplesize; int n = deadbeef->pcm_convert (&fileinfo->fmt, buffer, &outfmt, dspbuffer, frames * samplesize); memcpy (buffer, dspbuffer, n); sz = n; } outsize += sz; if (!header_written) { uint32_t size = (it->endsample-it->startsample) * outch * output_bps / 8; if (!size) { size = deadbeef->pl_get_item_duration (it) * fileinfo->fmt.samplerate * outch * output_bps / 8; } if (outsr != fileinfo->fmt.samplerate) { uint64_t temp = size; temp *= outsr; temp /= fileinfo->fmt.samplerate; size = temp; } memcpy (&wavehdr[22], &outch, 2); memcpy (&wavehdr[24], &outsr, 4); uint16_t blockalign = outch * output_bps / 8; memcpy (&wavehdr[32], &blockalign, 2); memcpy (&wavehdr[34], &output_bps, 2); fwrite (wavehdr, 1, wavehdr_size, temp_file); if (encoder_preset->method == DDB_ENCODER_METHOD_PIPE) { size = 0; } fwrite (&size, 1, sizeof (size), temp_file); header_written = 1; } int64_t res = fwrite (buffer, 1, sz, temp_file); if (sz != res) { fprintf (stderr, "converter: write error (%lld bytes written out of %d)\n", res, sz); goto error; } } if (abort && *abort) { goto error; } if (temp_file && temp_file != enc_pipe) { fseek (temp_file, wavehdr_size, SEEK_SET); fwrite (&outsize, 1, 4, temp_file); fclose (temp_file); temp_file = NULL; } if (encoder_preset->encoder[0] && encoder_preset->method == DDB_ENCODER_METHOD_FILE) { enc_pipe = popen (enc, "w"); } } } err = 0; error: if (temp_file && temp_file != enc_pipe) { fclose (temp_file); temp_file = NULL; } if (enc_pipe) { pclose (enc_pipe); enc_pipe = NULL; } if (dec && fileinfo) { dec->free (fileinfo); fileinfo = NULL; } if (abort && *abort && out[0]) { unlink (out); } if (input_file_name[0] && strcmp (input_file_name, "-")) { unlink (input_file_name); } // write junklib tags uint32_t tagflags = JUNK_STRIP_ID3V2 | JUNK_STRIP_APEV2 | JUNK_STRIP_ID3V1; if (encoder_preset->tag_id3v2) { tagflags |= JUNK_WRITE_ID3V2; } if (encoder_preset->tag_id3v1) { tagflags |= JUNK_WRITE_ID3V1; } if (encoder_preset->tag_apev2) { tagflags |= JUNK_WRITE_APEV2; } DB_playItem_t *out_it = deadbeef->pl_item_alloc (); deadbeef->pl_item_copy (out_it, it); deadbeef->pl_replace_meta (out_it, ":URI", out); deadbeef->pl_delete_meta (out_it, "cuesheet"); deadbeef->junk_rewrite_tags (out_it, tagflags, encoder_preset->id3v2_version + 3, "iso8859-1"); // write flac tags if (encoder_preset->tag_flac) { // find flac decoder plugin DB_decoder_t **plugs = deadbeef->plug_get_decoder_list (); DB_decoder_t *flac = NULL; for (int i = 0; plugs[i]; i++) { if (!strcmp (plugs[i]->plugin.id, "stdflac")) { flac = plugs[i]; break; } } if (!flac) { fprintf (stderr, "converter: flac plugin not found, cannot write flac metadata\n"); } else { if (0 != flac->write_metadata (out_it)) { fprintf (stderr, "converter: failed to write flac metadata, not a flac file?\n"); } } } // write vorbis tags if (encoder_preset->tag_oggvorbis) { // find flac decoder plugin DB_decoder_t **plugs = deadbeef->plug_get_decoder_list (); DB_decoder_t *ogg = NULL; for (int i = 0; plugs[i]; i++) { if (!strcmp (plugs[i]->plugin.id, "stdogg")) { ogg = plugs[i]; break; } } if (!ogg) { fprintf (stderr, "converter: ogg plugin not found, cannot write ogg metadata\n"); } else { if (0 != ogg->write_metadata (out_it)) { fprintf (stderr, "converter: failed to write ogg metadata, not an ogg file?\n"); } } } deadbeef->pl_item_unref (out_it); return err; }
void rg_calc_thread(void *ctx) { DB_decoder_t *dec = NULL; DB_fileinfo_t *fileinfo = NULL; char *buffer = NULL; char *bufferf = NULL; track_state_t *st = (track_state_t *)ctx; if (st->settings->pabort && *(st->settings->pabort)) { return; } if (deadbeef->pl_get_item_duration (st->settings->tracks[st->track_index]) <= 0) { st->settings->results[st->track_index].scan_result = DDB_RG_SCAN_RESULT_INVALID_FILE; return; } deadbeef->pl_lock (); dec = (DB_decoder_t *)deadbeef->plug_get_for_id (deadbeef->pl_find_meta (st->settings->tracks[st->track_index], ":DECODER")); deadbeef->pl_unlock (); if (dec) { fileinfo = dec->open (DDB_DECODER_HINT_RAW_SIGNAL); if (fileinfo && dec->init (fileinfo, DB_PLAYITEM (st->settings->tracks[st->track_index])) != 0) { st->settings->results[st->track_index].scan_result = DDB_RG_SCAN_RESULT_FILE_NOT_FOUND; goto error; } if (fileinfo) { st->gain_state[st->track_index] = ebur128_init(fileinfo->fmt.channels, fileinfo->fmt.samplerate, EBUR128_MODE_I); st->peak_state[st->track_index] = ebur128_init(fileinfo->fmt.channels, fileinfo->fmt.samplerate, EBUR128_MODE_SAMPLE_PEAK); // speaker mask mapping from WAV to EBUR128 static const int chmap[18] = { EBUR128_LEFT, EBUR128_RIGHT, EBUR128_CENTER, EBUR128_UNUSED, EBUR128_LEFT_SURROUND, EBUR128_RIGHT_SURROUND, EBUR128_LEFT_SURROUND, EBUR128_RIGHT_SURROUND, EBUR128_CENTER, EBUR128_LEFT_SURROUND, EBUR128_RIGHT_SURROUND, EBUR128_CENTER, EBUR128_LEFT_SURROUND, EBUR128_CENTER, EBUR128_RIGHT_SURROUND, EBUR128_LEFT_SURROUND, EBUR128_CENTER, EBUR128_RIGHT_SURROUND, }; uint32_t channelmask = fileinfo->fmt.channelmask; // first 18 speaker positions are known, the rest will be marked as UNUSED int ch = 0; for (int i = 0; i < 32 && ch < fileinfo->fmt.channels; i++) { if (i < 18) { if (channelmask & (1<<i)) { ebur128_set_channel (st->gain_state[st->track_index], ch, chmap[i]); ebur128_set_channel (st->peak_state[st->track_index], ch, chmap[i]); ch++; } } else { ebur128_set_channel (st->gain_state[st->track_index], ch, EBUR128_UNUSED); ebur128_set_channel (st->peak_state[st->track_index], ch, EBUR128_UNUSED); ch++; } } int samplesize = fileinfo->fmt.channels * fileinfo->fmt.bps / 8; int bs = 2000 * samplesize; ddb_waveformat_t fmt; buffer = malloc (bs); if (!fileinfo->fmt.is_float) { bufferf = malloc (2000 * sizeof (float) * fileinfo->fmt.channels); memcpy (&fmt, &fileinfo->fmt, sizeof (fmt)); fmt.bps = 32; fmt.is_float = 1; } else { bufferf = buffer; } int eof = 0; for (;;) { if (eof) { break; } if (st->settings->pabort && *(st->settings->pabort)) { break; } int sz = dec->read (fileinfo, buffer, bs); // read one block deadbeef->mutex_lock (st->settings->sync_mutex); int samplesize = fileinfo->fmt.channels * (fileinfo->fmt.bps >> 3); int numsamples = sz / samplesize; st->settings->cd_samples_processed += numsamples * 44100 / fileinfo->fmt.samplerate; deadbeef->mutex_unlock (st->settings->sync_mutex); if (sz != bs) { eof = 1; } // convert from native output to float, // only if the input is not float already if (!fileinfo->fmt.is_float) { deadbeef->pcm_convert (&fileinfo->fmt, buffer, &fmt, bufferf, sz); } int frames = sz / samplesize; ebur128_add_frames_float (st->gain_state[st->track_index], (float*) bufferf, frames); // collect data ebur128_add_frames_float (st->peak_state[st->track_index], (float*) bufferf, frames); // collect data } } if (!st->settings->pabort || !(*(st->settings->pabort))) { // calculating track peak // libEBUR128 calculates peak per channel, so we have to pick the highest value double tr_peak = 0; double ch_peak = 0; int res; for (int ch = 0; ch < fileinfo->fmt.channels; ++ch) { res = ebur128_sample_peak (st->peak_state[st->track_index], ch, &ch_peak); //trace ("rg_scanner: peak for ch %d: %f\n", ch, ch_peak); if (ch_peak > tr_peak) { //trace ("rg_scanner: %f > %f\n", ch_peak, tr_peak); tr_peak = ch_peak; } } st->settings->results[st->track_index].track_peak = (float) tr_peak; // calculate track loudness double loudness = st->settings->ref_loudness; ebur128_loudness_global (st->gain_state[st->track_index], &loudness); /* * EBUR128 sets the target level to -23 LUFS = 84dB * -> -23 - loudness = track gain to get to 84dB * * The old implementation of RG used 89dB, most people still use that * -> the above + (loudness - 84) = track gain to get to 89dB (or user specified) */ st->settings->results[st->track_index].track_gain = -23 - loudness + st->settings->ref_loudness - 84; } } error: // clean up if (fileinfo) { dec->free (fileinfo); } if (buffer && buffer != bufferf) { free (buffer); buffer = NULL; } if (bufferf) { free (bufferf); bufferf = NULL; } }