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.

500 lines
16 KiB
C++

/*
* MPD SACD Decoder plugin
* Copyright (c) 2011-2021 Maxim V.Anisiutkin <maxim.anisiutkin@gmail.com>
*
* This program 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.
*
* This program 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 "config.h"
#include "tag/Id3Scan.hxx"
#ifdef ENABLE_ID3TAG
#include <id3tag.h>
#endif
#include "sacd_dsdiff.h"
#define MARK_TIME(m) ((double)m.hours * 60 * 60 + (double)m.minutes * 60 + (double)m.seconds + ((double)m.samples + (double)m.offset) / (double)samplerate)
sacd_dsdiff_t::sacd_dsdiff_t() {
current_track = 0;
is_emaster = false;
is_dst_encoded = false;
}
sacd_dsdiff_t::~sacd_dsdiff_t() {
sacd_dsdiff_t::close();
}
uint32_t sacd_dsdiff_t::get_tracks() {
return get_tracks(track_area);
}
uint32_t sacd_dsdiff_t::get_tracks(area_id_e area_id) {
if ((area_id == AREA_TWOCH && channel_count == 2) || (area_id == AREA_MULCH && channel_count > 2) || area_id == AREA_BOTH) {
return track_index.size();
}
return 0;
}
uint32_t sacd_dsdiff_t::get_channels() {
return channel_count;
}
uint32_t sacd_dsdiff_t::get_loudspeaker_config() {
return loudspeaker_config;
}
uint32_t sacd_dsdiff_t::get_samplerate() {
return samplerate;
}
uint16_t sacd_dsdiff_t::get_framerate() {
return framerate;
}
uint64_t sacd_dsdiff_t::get_size() {
return current_size;
}
uint64_t sacd_dsdiff_t::get_offset() {
return sacd_media->get_position() - current_offset;
}
double sacd_dsdiff_t::get_duration() {
return get_duration(current_track);
}
double sacd_dsdiff_t::get_duration(uint32_t _track_index) {
if (_track_index < track_index.size()) {
double stop_time = is_emaster ? track_index[_track_index].stop_time2 : track_index[_track_index].stop_time1;
return stop_time - track_index[_track_index].start_time;
}
return 0.0;
}
bool sacd_dsdiff_t::is_dst() {
return is_dst_encoded;
}
bool sacd_dsdiff_t::open(sacd_media_t* _sacd_media, open_mode_e _mode) {
sacd_media = _sacd_media;
mode = _mode;
dsti_size = 0;
Chunk ck;
ID id;
bool skip_emaster_chunks = (mode & MODE_SINGLE_TRACK) == MODE_SINGLE_TRACK;
uint32_t start_mark_count = 0;
id3tags_t t_old;
track_index.resize(0);
id3tags.resize(0);
if (!sacd_media->seek(0)) {
return false;
}
if (!(sacd_media->read(&ck, sizeof(ck)) == sizeof(ck) && ck.has_id("FRM8"))) {
return false;
}
if (!(sacd_media->read(&id, sizeof(id)) == sizeof(id) && id.has_id("DSD "))) {
return false;
}
frm8_size = ck.get_size();
id3_offset = sizeof(ck) + ck.get_size();
while ((uint64_t)sacd_media->get_position() < frm8_size + sizeof(ck)) {
if (!(sacd_media->read(&ck, sizeof(ck)) == sizeof(ck))) {
return false;
}
if (ck.has_id("FVER") && ck.get_size() == 4) {
if (!(sacd_media->read(&version, sizeof(version)) == sizeof(version))) {
return false;
}
version = hton32(version);
}
else if (ck.has_id("PROP")) {
if (!(sacd_media->read(&id, sizeof(id)) == sizeof(id) && id.has_id("SND "))) {
return false;
}
int64_t id_prop_end = sacd_media->get_position() - sizeof(id) + ck.get_size();
while (sacd_media->get_position() < id_prop_end) {
if (!(sacd_media->read(&ck, sizeof(ck)) == sizeof(ck))) {
return false;
}
if (ck.has_id("FS ") && ck.get_size() == 4) {
if (!(sacd_media->read(&samplerate, sizeof(samplerate)) == sizeof(samplerate))) {
return false;
}
samplerate = hton32(samplerate);
}
else if (ck.has_id("CHNL")) {
if (!(sacd_media->read(&channel_count, sizeof(channel_count)) == sizeof(channel_count))) {
return false;
}
channel_count = hton16(channel_count);
switch (channel_count) {
case 2:
loudspeaker_config = 0;
break;
case 5:
loudspeaker_config = 3;
break;
case 6:
loudspeaker_config = 4;
break;
default:
loudspeaker_config = 65535;
break;
}
sacd_media->skip(ck.get_size() - sizeof(channel_count));
}
else if (ck.has_id("CMPR")) {
if (!(sacd_media->read(&id, sizeof(id)) == sizeof(id))) {
return false;
}
if (id.has_id("DSD ")) {
is_dst_encoded = false;
}
if (id.has_id("DST ")) {
is_dst_encoded = true;
}
sacd_media->skip(ck.get_size() - sizeof(id));
}
else if (ck.has_id("LSCO")) {
if (!(sacd_media->read(&loudspeaker_config, sizeof(loudspeaker_config)) == sizeof(loudspeaker_config))) {
return false;
}
loudspeaker_config = hton16(loudspeaker_config);
sacd_media->skip(ck.get_size() - sizeof(loudspeaker_config));
}
else if (ck.has_id("ID3 ")) {
t_old.index = 0;
t_old.offset = sacd_media->get_position();
t_old.tag_value.resize((uint32_t)ck.get_size());
sacd_media->read(t_old.tag_value.data(), t_old.tag_value.size());
}
else {
sacd_media->skip(ck.get_size());
}
sacd_media->skip(sacd_media->get_position() & 1);
}
}
else if (ck.has_id("DSD ")) {
data_offset = sacd_media->get_position();
data_size = ck.get_size();
framerate = 75;
dsd_frame_size = samplerate / 8 * channel_count / framerate;
frame_count = (uint32_t)(data_size / dsd_frame_size);
sacd_media->skip(ck.get_size());
track_t s;
s.start_time = 0.0;
s.stop_time1 = s.stop_time2 = (double)frame_count / framerate;
track_index.push_back(s);
}
else if (ck.has_id("DST ")) {
data_offset = sacd_media->get_position();
data_size = ck.get_size();
if (!(sacd_media->read(&ck, sizeof(ck)) == sizeof(ck) && ck.has_id("FRTE") && ck.get_size() == 6)) {
return false;
}
data_offset += sizeof(ck) + ck.get_size();
data_size -= sizeof(ck) + ck.get_size();
current_offset = data_offset;
current_size = data_size;
if (!(sacd_media->read(&frame_count, sizeof(frame_count)) == sizeof(frame_count))) {
return false;
}
frame_count = hton32(frame_count);
if (!(sacd_media->read(&framerate, sizeof(framerate)) == sizeof(framerate))) {
return false;
}
framerate = hton16(framerate);
dsd_frame_size = samplerate / 8 * channel_count / framerate;
sacd_media->seek(data_offset + data_size);
track_t s;
s.start_time = 0.0;
s.stop_time1 = s.stop_time2 = (double)frame_count / framerate;
track_index.push_back(s);
}
else if (ck.has_id("DSTI")) {
dsti_offset = sacd_media->get_position();
dsti_size = ck.get_size();
sacd_media->skip(ck.get_size());
}
else if (ck.has_id("DIIN") && !skip_emaster_chunks) {
int64_t id_diin_end = sacd_media->get_position() + ck.get_size();
while (sacd_media->get_position() < id_diin_end) {
if (!(sacd_media->read(&ck, sizeof(ck)) == sizeof(ck))) {
return false;
}
if (ck.has_id("MARK") && ck.get_size() >= sizeof(Marker)) {
Marker m;
if (sacd_media->read(&m, sizeof(Marker)) == sizeof(Marker)) {
m.hours = hton16(m.hours);
m.samples = hton32(m.samples);
m.offset = hton32(m.offset);
m.markType = hton16(m.markType);
m.markChannel = hton16(m.markChannel);
m.TrackFlags = hton16(m.TrackFlags);
m.count = hton32(m.count);
switch (m.markType) {
case TrackStart:
if (start_mark_count > 0) {
track_t s;
track_index.push_back(s);
}
start_mark_count++;
if (track_index.size() > 0) {
track_index[track_index.size() - 1].start_time = MARK_TIME(m);
track_index[track_index.size() - 1].stop_time2 = (double)frame_count / framerate;
track_index[track_index.size() - 1].stop_time1 = track_index[track_index.size() - 1].stop_time2;
if (track_index.size() - 1 > 0) {
if (track_index[track_index.size() - 2].stop_time2 > track_index[track_index.size() - 1].start_time) {
track_index[track_index.size() - 2].stop_time2 = track_index[track_index.size() - 1].start_time;
track_index[track_index.size() - 2].stop_time1 = track_index[track_index.size() - 2].stop_time2;
}
}
}
break;
case TrackStop:
if (track_index.size() > 0) {
track_index[track_index.size() - 1].stop_time1 = MARK_TIME(m);
}
break;
}
}
sacd_media->skip(ck.get_size() - sizeof(Marker));
}
else {
sacd_media->skip(ck.get_size());
}
sacd_media->skip(sacd_media->get_position() & 1);
}
}
else if (ck.has_id("ID3 ")) {
id3_offset = std::min(id3_offset, (uint64_t)sacd_media->get_position() - sizeof(ck));
id3tags_t t;
t.index = id3tags.size();
t.offset = sacd_media->get_position();
t.tag_value.resize((uint32_t)ck.get_size());
sacd_media->read(t.tag_value.data(), t.tag_value.size());
id3tags.push_back(t);
}
else {
sacd_media->skip(ck.get_size());
}
sacd_media->skip(sacd_media->get_position() & 1);
}
if (id3tags.size() == 0) {
if (t_old.tag_value.size() > 0) {
id3tags.push_back(t_old);
}
}
sacd_media->seek(data_offset);
set_emaster(false);
index_id3tags();
return track_index.size() > 0;
}
bool sacd_dsdiff_t::close() {
current_track = 0;
track_index.resize(0);
id3tags.resize(0);
dsti_size = 0;
if (!sacd_media->seek(0)) {
return false;
}
return true;
}
void sacd_dsdiff_t::select_area(area_id_e area_id) {
track_area = area_id;
}
void sacd_dsdiff_t::set_emaster(bool emaster) {
is_emaster = emaster;
}
bool sacd_dsdiff_t::select_track(uint32_t _track_index, area_id_e area_id, uint32_t _offset) {
(void)area_id;
if (_track_index < track_index.size()) {
current_track = _track_index;
double t0 = track_index[current_track].start_time;
double t1 = is_emaster ? track_index[current_track].stop_time2 : track_index[current_track].stop_time1;
uint64_t offset = (uint64_t)(t0 * framerate / frame_count * data_size) + _offset;
uint64_t size = (uint64_t)(t1 * framerate / frame_count * data_size) - offset;
if (is_dst_encoded) {
if (dsti_size > 0) {
if ((uint32_t)(t0 * framerate) < (uint32_t)(dsti_size / sizeof(DSTFrameIndex) - 1)) {
current_offset = get_dsti_for_frame((uint32_t)(t0 * framerate));
}
else {
current_offset = data_offset + offset;
}
if ((uint32_t)(t1 * framerate) < (uint32_t)(dsti_size / sizeof(DSTFrameIndex) - 1)) {
current_size = get_dsti_for_frame((uint32_t)(t1 * framerate)) - current_offset;
}
else {
current_size = data_size;
}
}
else {
current_offset = data_offset + offset;
current_size = size;
}
}
else {
current_offset = data_offset + (offset / dsd_frame_size) * dsd_frame_size;
current_size = (size / dsd_frame_size) * dsd_frame_size;
}
}
sacd_media->seek(current_offset);
return true;
}
bool sacd_dsdiff_t::read_frame(uint8_t* frame_data, size_t* frame_size, frame_type_e* frame_type) {
//static uint64_t s_next_frame = 0;
//if (sacd_media->get_position() != s_next_frame) {
// console::printf("offset: %d - %d", (uint32_t)sacd_media->get_position(), (uint32_t)s_next_frame);
//}
if (is_dst_encoded) {
Chunk ck;
while ((uint64_t)sacd_media->get_position() < current_offset + current_size && sacd_media->read(&ck, sizeof(ck)) == sizeof(ck)) {
if (ck.has_id("DSTF") && ck.get_size() <= (uint64_t)*frame_size) {
if (sacd_media->read(frame_data, (size_t)ck.get_size()) == ck.get_size()) {
sacd_media->skip(ck.get_size() & 1);
*frame_size = (size_t)ck.get_size();
*frame_type = FRAME_DST;
//s_next_frame = sacd_media->get_position();
return true;
}
break;
}
else if (ck.has_id("DSTC") && ck.get_size() == 4) {
uint32_t crc;
if (ck.get_size() == sizeof(crc)) {
if (sacd_media->read(&crc, sizeof(crc)) != sizeof(crc)) {
break;
}
}
else {
sacd_media->skip(ck.get_size());
sacd_media->skip(ck.get_size() & 1);
}
}
else {
sacd_media->seek(sacd_media->get_position() + 1 - (int)sizeof(ck));
}
}
}
else {
int64_t position = sacd_media->get_position();
*frame_size = (size_t)std::min((int64_t)*frame_size, std::max((int64_t)0, (int64_t)(current_offset + current_size) - position));
if (*frame_size > 0) {
*frame_size = sacd_media->read(frame_data, *frame_size);
*frame_size -= *frame_size % channel_count;
if (*frame_size > 0) {
*frame_type = FRAME_DSD;
//s_next_frame = sacd_media->get_position();
return true;
}
}
}
*frame_type = FRAME_INVALID;
return false;
}
bool sacd_dsdiff_t::seek(double seconds) {
uint64_t offset = std::min((uint64_t)(get_size() * seconds / get_duration()), get_size());
if (is_dst_encoded) {
if (dsti_size > 0) {
uint32_t frame = std::min((uint32_t)((track_index[current_track].start_time + seconds) * framerate), frame_count - 1);
if (frame < (uint32_t)(dsti_size / sizeof(DSTFrameIndex) - 1)) {
offset = get_dsti_for_frame(frame) - current_offset;
}
}
}
else {
offset = (offset / dsd_frame_size) * dsd_frame_size;
}
sacd_media->seek(current_offset + offset);
return true;
}
void sacd_dsdiff_t::get_info(uint32_t _track_index, TagHandler& handler) {
for (uint32_t i = 0; i < id3tags.size(); i++) {
if (_track_index == id3tags[i].index) {
get_id3tags(_track_index, handler);
break;
}
}
}
uint64_t sacd_dsdiff_t::get_dsti_for_frame(uint32_t frame_nr) {
uint64_t cur_offset;
DSTFrameIndex frame_index;
cur_offset = sacd_media->get_position();
frame_nr = std::min(frame_nr, (uint32_t)(dsti_size / sizeof(DSTFrameIndex) - 1));
sacd_media->seek(dsti_offset + frame_nr * sizeof(DSTFrameIndex));
cur_offset = sacd_media->get_position();
sacd_media->read(&frame_index, sizeof(DSTFrameIndex));
sacd_media->seek(cur_offset);
return hton64(frame_index.offset) - sizeof(Chunk);
}
void sacd_dsdiff_t::get_id3tags(uint32_t _track_index, TagHandler& handler) {
#ifdef ENABLE_ID3TAG
id3_byte_t* tag_value = static_cast<id3_byte_t*>(id3tags[_track_index].tag_value.data());
id3_length_t tag_size = static_cast<id3_length_t>(id3tags[_track_index].tag_value.size());
if (tag_size > 0 && tag_value) {
struct id3_tag* id3_tag = id3_tag_parse(tag_value, tag_size);
if (id3_tag != nullptr) {
if ((mode & MODE_SINGLE_TRACK) == MODE_SINGLE_TRACK) {
//scan_id3_tag(id3_tag, handler, handler_ctx);
}
else {
scan_id3_tag(id3_tag, handler);
}
id3_tag_delete(id3_tag);
}
}
#endif
}
void sacd_dsdiff_t::index_id3tags() {
#ifdef ENABLE_ID3TAG
/*
for (uint32_t i = 0; i < id3tags.size(); i++) {
if (id3tags[i].size > 0) {
id3_byte_t* dsdid3 = (id3_byte_t*)&id3tags[i].data[0];
const id3_length_t count = id3tags[i].size;
struct id3_tag* id3_tag = id3_tag_parse(dsdid3, count);
if (id3_tag != nullptr) {
const struct id3_frame* frame;
frame = id3_tag_findframe(id3_tag, ID3_FRAME_TRACK, 0);
if (frame != nullptr) {
const id3_field* field = id3_frame_field(frame, 0);
if (field != nullptr) {
long tracknumber = id3_field_getint(field);
id3tags[i].index = tracknumber;
}
}
id3_tag_delete(id3_tag);
}
}
}
*/
#endif
}