diff --git a/src/input/plugins/CdioParanoiaInputPlugin.cxx b/src/input/plugins/CdioParanoiaInputPlugin.cxx index f9c916886..2a96b6082 100644 --- a/src/input/plugins/CdioParanoiaInputPlugin.cxx +++ b/src/input/plugins/CdioParanoiaInputPlugin.cxx @@ -23,6 +23,7 @@ #include "config.h" #include "CdioParanoiaInputPlugin.hxx" +#include "lib/cdio/Paranoia.hxx" #include "../InputStream.hxx" #include "../InputPlugin.hxx" #include "util/TruncateString.hxx" @@ -41,19 +42,12 @@ #include #include -#include -#if LIBCDIO_VERSION_NUM >= 90 -#include -#else -#include -#endif - #include class CdioParanoiaInputStream final : public InputStream { cdrom_drive_t *const drv; CdIo_t *const cdio; - cdrom_paranoia_t *const para; + CdromParanoia para; const lsn_t lsn_from, lsn_to; int lsn_relofs; @@ -67,18 +61,17 @@ class CdioParanoiaInputStream final : public InputStream { bool reverse_endian, lsn_t _lsn_from, lsn_t _lsn_to) :InputStream(_uri, _mutex), - drv(_drv), cdio(_cdio), para(cdio_paranoia_init(drv)), + drv(_drv), cdio(_cdio), para(drv), lsn_from(_lsn_from), lsn_to(_lsn_to), lsn_relofs(0), buffer_lsn(-1) { /* Set reading mode for full paranoia, but allow skipping sectors. */ - paranoia_modeset(para, - PARANOIA_MODE_FULL^PARANOIA_MODE_NEVERSKIP); + para.SetMode(PARANOIA_MODE_FULL^PARANOIA_MODE_NEVERSKIP); /* seek to beginning of the track */ - cdio_paranoia_seek(para, lsn_from, SEEK_SET); + para.Seek(lsn_from); seekable = true; size = (lsn_to - lsn_from + 1) * CDIO_CD_FRAMESIZE_RAW; @@ -91,7 +84,7 @@ class CdioParanoiaInputStream final : public InputStream { } ~CdioParanoiaInputStream() { - cdio_paranoia_free(para); + para = {}; cdio_cddap_close_no_free_cdio(drv); cdio_destroy(cdio); } @@ -278,7 +271,7 @@ CdioParanoiaInputStream::Seek(offset_type new_offset) { const ScopeUnlock unlock(mutex); - cdio_paranoia_seek(para, lsn_from + lsn_relofs, SEEK_SET); + para.Seek(lsn_from + lsn_relofs); } } @@ -298,20 +291,22 @@ CdioParanoiaInputStream::Read(void *ptr, size_t length) if (lsn_relofs != buffer_lsn) { const ScopeUnlock unlock(mutex); - rbuf = cdio_paranoia_read(para, nullptr); - - char *s_err = cdio_cddap_errors(drv); - if (s_err) { - FormatError(cdio_domain, - "paranoia_read: %s", s_err); + try { + rbuf = para.Read().data; + } catch (...) { + char *s_err = cdio_cddap_errors(drv); + if (s_err) { + FormatError(cdio_domain, + "paranoia_read: %s", s_err); #if LIBCDIO_VERSION_NUM >= 90 - cdio_cddap_free_messages(s_err); + cdio_cddap_free_messages(s_err); #else - free(s_err); + free(s_err); #endif + } + + throw; } - if (!rbuf) - throw std::runtime_error("paranoia read error"); //store current buffer memcpy(buffer, rbuf, CDIO_CD_FRAMESIZE_RAW); diff --git a/src/lib/cdio/Paranoia.hxx b/src/lib/cdio/Paranoia.hxx new file mode 100644 index 000000000..51e9effaa --- /dev/null +++ b/src/lib/cdio/Paranoia.hxx @@ -0,0 +1,156 @@ +/* + * Copyright 2019 Max Kellermann + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CDIO_PARANOIA_HXX +#define CDIO_PARANOIA_HXX + +#include "util/ConstBuffer.hxx" +#include "util/Compiler.h" + +#include +#if LIBCDIO_VERSION_NUM >= 90 +#include +#else +#include +#endif + +#include +#include + +class CdromDrive { + cdrom_drive_t *drv = nullptr; + +public: + CdromDrive() = default; + + explicit CdromDrive(CdIo_t *cdio) + :drv(cdio_cddap_identify_cdio(cdio, 1, nullptr)) + { + if (drv == nullptr) + throw std::runtime_error("Failed to identify audio CD"); + + cdda_verbose_set(drv, CDDA_MESSAGE_FORGETIT, + CDDA_MESSAGE_FORGETIT); + } + + ~CdromDrive() noexcept { + if (drv != nullptr) + cdio_cddap_close_no_free_cdio(drv); + } + + CdromDrive(CdromDrive &&src) noexcept + :drv(std::exchange(src.drv, nullptr)) {} + + CdromDrive &operator=(CdromDrive &&src) noexcept { + using std::swap; + swap(drv, src.drv); + return *this; + } + + auto get() const noexcept { + return drv; + } + + void Open() { + if (cdio_cddap_open(drv) != 0) + throw std::runtime_error("Failed to open disc"); + } + + gcc_pure + bool IsAudioTrack(track_t i) const noexcept { + return cdio_cddap_track_audiop(drv, i); + } + + auto GetTrackSectorRange(track_t i) const { + auto first = cdio_cddap_track_firstsector(drv, i); + auto last = cdio_cddap_track_lastsector(drv, i); + if (first < 0 || last < 0) + throw std::runtime_error("Invalid track number"); + return std::make_pair(first, last); + } + + gcc_pure + unsigned GetTrackCount() const noexcept { + return cdio_cddap_tracks(drv); + } + + unsigned GetTrackChannels(track_t i) const { + auto value = cdio_cddap_track_channels(drv, i); + if (value < 0) + throw std::runtime_error("cdio_cddap_track_channels() failed"); + return unsigned(value); + } +}; + +class CdromParanoia { + cdrom_paranoia_t *paranoia = nullptr; + +public: + CdromParanoia() = default; + + explicit CdromParanoia(cdrom_drive_t *drv) noexcept + :paranoia(cdio_paranoia_init(drv)) {} + + ~CdromParanoia() noexcept { + if (paranoia != nullptr) + cdio_paranoia_free(paranoia); + } + + CdromParanoia(CdromParanoia &&src) noexcept + :paranoia(std::exchange(src.paranoia, nullptr)) {} + + CdromParanoia &operator=(CdromParanoia &&src) noexcept { + using std::swap; + swap(paranoia, src.paranoia); + return *this; + } + + auto get() const noexcept { + return paranoia; + } + + void SetMode(int mode_flags) noexcept { + paranoia_modeset(paranoia, mode_flags); + } + + void Seek(int32_t seek, int whence=SEEK_SET) { + if (cdio_paranoia_seek(paranoia, seek, whence) < 0) + throw std::runtime_error("Failed to seek disc"); + } + + ConstBuffer Read() { + const int16_t *data = cdio_paranoia_read(paranoia, nullptr); + if (data == nullptr) + throw std::runtime_error("Read from audio CD failed"); + + return {data, CDIO_CD_FRAMESIZE_RAW / sizeof(int16_t)}; + } +}; + +#endif