You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

388 lines
13 KiB
C++

/*
* MPD DVD-Audio Decoder plugin
* Copyright (c) 2014-2025 Maxim V.Anisiutkin <maxim.anisiutkin@gmail.com>
*
* DVD-Audio Decoder is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* DVD-Audio Decoder is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with FFmpeg; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "dvda_disc.h"
#include "mlp_audio_stream.h"
#include "pcm_audio_stream.h"
#include "config.h"
#include <malloc.h>
#include <string>
#include "Log.hxx"
#include "tag/Tag.hxx"
#include "util/Domain.hxx"
static constexpr Domain dvdaiso_domain("dvdaiso");
dvda_disc_t::dvda_disc_t() {
dvda_media = nullptr;
stream_downmix = false;
sel_track_index = -1;
}
dvda_disc_t::~dvda_disc_t() {
close();
}
dvda_filesystem_t* dvda_disc_t::get_filesystem() {
return dvda_filesystem.get();
}
audio_track_t dvda_disc_t::get_track(uint32_t track_index) {
return track_list.get_track_by_index(track_index);
}
uint32_t dvda_disc_t::get_tracks() {
return track_list.size();
}
uint32_t dvda_disc_t::get_channels() {
auto info = track_list.get_track_by_index(sel_track_index).audio_stream_info;
return info.group1_channels + info.group2_channels;
}
uint32_t dvda_disc_t::get_loudspeaker_config() {
return 0;
}
uint32_t dvda_disc_t::get_samplerate() {
return track_list.get_track_by_index(sel_track_index).audio_stream_info.group1_samplerate;
}
double dvda_disc_t::get_duration() {
return track_list.get_track_by_index(sel_track_index).duration;
}
double dvda_disc_t::get_duration(uint32_t track_index) {
if (track_index < (uint32_t)track_list.size()) {
return track_list.get_track_by_index(track_index).duration;
}
return 0.0;
}
bool dvda_disc_t::can_downmix() {
return track_list.get_track_by_index(sel_track_index).audio_stream_info.can_downmix;
}
void dvda_disc_t::get_info(uint32_t track_index, bool downmix, TagHandler& handler) {
if (!(track_index < (uint32_t)track_list.size())) {
return;
}
auto info = track_list.get_track_by_index(track_index).audio_stream_info;
//int ts = track_list[track_index].dvda_titleset;
//int ti = track_list[track_index].dvda_title;
auto tr = track_list.get_track_by_index(track_index).dvda_track;
char disc_label[32];
bool label_ok = dvda_filesystem->get_name(disc_label);
disc_label[31] = '\0';
std::string disc_path = dvda_media->get_name();
size_t s0 = disc_path.rfind('/');
size_t s1 = disc_path.rfind('.');
std::string disc_name;
if (s0 != std::string::npos && s1 != std::string::npos) {
disc_name = disc_path.substr(s0 + 1, s1 - s0 - 1);
}
std::string tag_value;
tag_value = label_ok ? disc_label : "DVD-Audio";
handler.OnTag(TAG_DISC, tag_value.c_str());
// Clean album title - no format info appended
tag_value = !disc_name.empty() ? disc_name : "Album";
handler.OnTag(TAG_ALBUM, tag_value.c_str());
// Store channel/format info in comment tag for reference
std::string comment_value = "DVDA_";
if (!downmix) {
comment_value += std::to_string(info.group1_channels + info.group2_channels);
comment_value += "CH_";
}
else {
comment_value += "DMX_";
}
comment_value += info.stream_id == MLP_STREAM_ID ? (info.stream_type == STREAM_TYPE_MLP ? "MLP" : "TrueHD") : "PCM";
handler.OnTag(TAG_COMMENT, comment_value.c_str());
tag_value = "Artist";
handler.OnTag(TAG_ARTIST, tag_value.c_str());
char track_number_string[16];
//sprintf(track_number_string, "%02d.%02d.%02d", ts, ti, tr);
sprintf(track_number_string, "%02d", tr);
// Clean track title - just track number and name
tag_value = track_number_string;
tag_value += " - ";
tag_value += "Track " + std::to_string(tr);
handler.OnTag(TAG_TITLE, tag_value.c_str());
tag_value = "Composer";
handler.OnTag(TAG_COMPOSER, tag_value.c_str());
tag_value = "Performer";
handler.OnTag(TAG_PERFORMER, tag_value.c_str());
tag_value = "Genre";
handler.OnTag(TAG_GENRE, tag_value.c_str());
}
bool dvda_disc_t::open(dvda_media_t* _dvda_media) {
if (!close()) {
return false;
}
dvda_media = _dvda_media;
dvda_filesystem = std::make_unique<dvda_filesystem_t>();
if (!dvda_filesystem) {
return false;
}
if (!dvda_filesystem->mount(dvda_media)) {
return false;
}
dvda_zone = std::make_unique<dvda_zone_t>(*dvda_filesystem);
if (!dvda_zone) {
return false;
}
if (!dvda_zone->open()) {
return false;
}
if (!(dvda_zone->get_titlesets().size() > 0)) {
return false;
}
track_list.init(*dvda_zone);
return track_list.size() > 0;
}
bool dvda_disc_t::close() {
track_list.clear();
if (dvda_zone) {
dvda_zone->close();
}
dvda_zone.reset();
dvda_filesystem.reset();
dvda_media = nullptr;
sel_track_index = -1;
return true;
}
bool dvda_disc_t::select_track(uint32_t track_index, size_t offset) {
sel_track_index = track_index;
sel_track_offset = offset;
audio_track = track_list.get_track_by_index(sel_track_index);
sel_titleset_index = audio_track.dvda_titleset - 1;
track_stream.init(512 * DVD_BLOCK_SIZE, 4 * DVD_BLOCK_SIZE, 16 * DVD_BLOCK_SIZE);
ps1_data.resize(16 * DVD_BLOCK_SIZE);
stream_block_current = audio_track.block_first;
stream_size = (audio_track.block_last + 1 - audio_track.block_first) * DVD_BLOCK_SIZE;
stream_ps1_info.header.stream_id = UNK_STREAM_ID;
stream_duration = audio_track.duration;
stream_needs_reinit = false;
major_sync_0 = false;
return true;
}
bool dvda_disc_t::get_downmix() {
return stream_downmix;
}
bool dvda_disc_t::set_downmix(bool downmix) {
if (downmix && !audio_track.audio_stream_info.can_downmix) {
return false;
}
stream_downmix = downmix;
return true;
}
bool dvda_disc_t::read_frame(uint8_t* frame_data, size_t* frame_size) {
decode_run_read_stream_start:
if (track_stream.is_ready_to_write() && !stream_needs_reinit) {
stream_buffer_read();
}
int data_size = *frame_size, bytes_decoded = 0;
bytes_decoded = (audio_stream ? audio_stream->decode(frame_data, &data_size, track_stream.get_read_ptr(), track_stream.get_read_size()) : 0);
if (bytes_decoded <= 0) {
track_stream.move_read_ptr(0);
if (bytes_decoded == audio_stream_t::RETCODE_EXCEPT) {
LogFmt(LogLevel::ERROR, dvdaiso_domain, "Exception occured in DVD-Audio Decoder");
return false;
}
bool decoder_needs_reinit = (bytes_decoded == audio_stream_t::RETCODE_REINIT);
if (decoder_needs_reinit) {
audio_stream.reset();
LogFmt(LogLevel::WARNING, dvdaiso_domain, "Reinitializing DVD-Audio Decoder: MLP/TrueHD");
goto decode_run_read_stream_start;
}
if (track_stream.get_read_size() == 0) {
if (stream_needs_reinit) {
stream_needs_reinit = false;
audio_stream.reset();
stream_ps1_info.header.stream_id = UNK_STREAM_ID;
LogFmt(LogLevel::WARNING, dvdaiso_domain, "Reinitializing DVD-Audio Decoder: PCM");
goto decode_run_read_stream_start;
}
else {
return false;
}
}
if (audio_stream) {
int major_sync = audio_stream->resync(track_stream.get_read_ptr(), track_stream.get_read_size());
if (major_sync == 0) {
if (major_sync_0) {
if (track_stream.get_read_size() > 4)
major_sync = audio_stream->resync(track_stream.get_read_ptr() + 1, track_stream.get_read_size() - 1);
}
else
major_sync_0 = true;
}
if (major_sync < 0) {
if (stream_needs_reinit)
major_sync = track_stream.get_read_size();
else
major_sync = track_stream.get_read_size() > 4 ? track_stream.get_read_size() - 4 : 0;
if (major_sync <= 0)
return false;
}
if (major_sync > 0) {
track_stream.move_read_ptr(major_sync);
LogFmt(LogLevel::ERROR, dvdaiso_domain, "DVD-Audio Decoder is out of sync: %d bytes skipped", major_sync);
}
goto decode_run_read_stream_start;
}
else {
create_audio_stream(stream_ps1_info, track_stream.get_read_ptr(), track_stream.get_read_size(), stream_downmix);
if (audio_stream) {
if (audio_stream->get_downmix()) {
audio_stream->set_downmix_coef(audio_track.LR_dmx_coef);
}
audio_stream->set_check(false);
track_stream.move_read_ptr(audio_stream->get_info().sync_offset);
}
else {
track_stream.move_read_ptr(DVD_BLOCK_SIZE);
stream_ps1_info.header.stream_id = UNK_STREAM_ID;
LogFmt(LogLevel::ERROR, dvdaiso_domain, "DVD-Audio Decoder initialization failed");
}
goto decode_run_read_stream_start;
}
return false;
}
major_sync_0 = false;
track_stream.move_read_ptr(bytes_decoded);
*frame_size = data_size;
return true;
}
bool dvda_disc_t::seek(double seconds) {
track_stream.reinit();
audio_stream.reset();
uint32_t offset = (uint32_t)((seconds / (audio_track.duration + 1.0)) * (double)(audio_track.block_last + 1 - audio_track.block_first));
if (offset > audio_track.block_last - audio_track.block_first - 1) {
offset = audio_track.block_last - audio_track.block_first - 1;
}
stream_block_current = audio_track.block_first + offset;
stream_ps1_info.header.stream_id = UNK_STREAM_ID;
return true;
}
bool dvda_disc_t::create_audio_stream(sub_header_t& p_ps1_info, uint8_t* p_buf, int p_buf_size, bool p_downmix) {
audio_stream.reset();
int init_code = -1;
switch (stream_ps1_info.header.stream_id) {
case MLP_STREAM_ID:
audio_stream = std::make_unique<mlp_audio_stream_t>();
if (audio_stream) {
init_code = audio_stream->init(p_buf, p_buf_size, p_downmix);
}
break;
case PCM_STREAM_ID:
audio_stream = std::make_unique<pcm_audio_stream_t>();
if (audio_stream) {
init_code = audio_stream->init((uint8_t*)&stream_ps1_info.extra_header, p_ps1_info.header.extra_header_length, p_downmix);
}
break;
default:
break;
}
if (!audio_stream) {
return false;
}
if (init_code < 0) {
audio_stream.reset();
return false;
}
auto info = audio_stream->get_info();
stream_samplerate = info.group1_samplerate;
stream_bits = info.group1_bits > 16 ? 32 : 16;
if (audio_stream->get_downmix()) {
stream_channels = 2;
//stream_channel_map = audio_chunk_t::g_guess_channel_config(pcm_out_channels);
}
else {
stream_channels = info.group1_channels + info.group2_channels;
//stream_channel_map = audio_chunk_t::g_channel_config_from_wfx(audio_stream->get_wfx_channels());
}
return true;
}
void dvda_disc_t::stream_buffer_read() {
sub_header_t ps1_info;
int blocks_to_read, blocks_read, bytes_written = 0;
blocks_to_read = track_stream.get_write_size() / DVD_BLOCK_SIZE;
if (stream_block_current <= audio_track.block_last) {
if (stream_block_current + blocks_to_read > audio_track.block_last + 1) {
blocks_to_read = audio_track.block_last + 1 - stream_block_current;
}
blocks_read = dvda_zone->get_blocks(sel_titleset_index, stream_block_current, blocks_to_read, track_stream.get_write_ptr());
dvda_block_t::get_ps1(track_stream.get_write_ptr(), blocks_read, ps1_data.data(), &bytes_written, &ps1_info);
memcpy(track_stream.get_write_ptr(), ps1_data.data(), bytes_written);
track_stream.move_write_ptr(bytes_written);
if (stream_ps1_info.header.stream_id == UNK_STREAM_ID) {
stream_ps1_info = ps1_info;
}
if (blocks_read < blocks_to_read) {
LogFmt(LogLevel::ERROR, dvdaiso_domain, "DVD-Audio Decoder cannot read track data: titleset = %d, block_number = %d, blocks_to_read = %d", sel_titleset_index, stream_block_current + blocks_read, blocks_to_read - blocks_read);
}
stream_block_current += blocks_to_read;
if (stream_block_current > audio_track.block_last) {
int blocks_after_last = dvda_zone->get_titleset(sel_titleset_index).get_last() - audio_track.block_last;
int blocks_to_sync = blocks_after_last < 8 ? blocks_after_last : 8;
if (stream_block_current <= audio_track.block_last + blocks_to_sync) {
if (stream_block_current + blocks_to_read > audio_track.block_last + 1 + blocks_to_sync) {
blocks_to_read = audio_track.block_last + 1 + blocks_to_sync - stream_block_current;
}
blocks_read = dvda_zone->get_blocks(sel_titleset_index, stream_block_current, blocks_to_read, track_stream.get_write_ptr());
bytes_written = 0;
dvda_block_t::get_ps1(track_stream.get_write_ptr(), blocks_read, ps1_data.data(), &bytes_written, nullptr);
memcpy(track_stream.get_write_ptr(), ps1_data.data(), bytes_written);
if (audio_stream) {
int major_sync = audio_stream->resync(track_stream.get_write_ptr(), bytes_written);
if (major_sync > 0) {
track_stream.move_write_ptr(major_sync);
}
}
if (blocks_read < blocks_to_read) {
LogFmt(LogLevel::ERROR, dvdaiso_domain, "DVD-Audio Decoder cannot read track tail: titleset = %d, block_number = %d, blocks_to_read = %d", sel_titleset_index, stream_block_current + blocks_read, blocks_to_read - blocks_read);
}
stream_block_current += blocks_to_read;
}
}
}
}