Skip to content

Commit 610d6e7

Browse files
committed
MultiStreamingDecoder: setMimeSource()
1 parent ba6e887 commit 610d6e7

File tree

4 files changed

+194
-46
lines changed

4 files changed

+194
-46
lines changed

src/AudioTools/AudioCodecs/MultiDecoder.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class MultiDecoder : public AudioDecoder {
2323
/// Default constructor
2424
MultiDecoder() = default;
2525
/// Provides a URLStream to look up the mime type from the http reply header
26-
MultiDecoder(AbstractURLStream& url) { setMimeSource(url); }
26+
MultiDecoder(MimeSource& mimeSource) { setMimeSource(mimeSource); }
2727

2828
/// Enables the automatic mime type determination
2929
bool begin() override {
@@ -68,7 +68,7 @@ class MultiDecoder : public AudioDecoder {
6868

6969
/// Defines url stream from which we determine the mime type from the reply
7070
/// header
71-
void setMimeSource(AbstractURLStream& url) { p_url_stream = &url; }
71+
void setMimeSource(MimeSource& mimeSource) { p_mime_source = &mimeSource; }
7272

7373
/// selects the actual decoder by mime type - this is usually called
7474
/// automatically from the determined mime type
@@ -113,9 +113,9 @@ class MultiDecoder : public AudioDecoder {
113113
size_t write(const uint8_t* data, size_t len) override {
114114
if (is_first) {
115115
const char* mime = nullptr;
116-
if (p_url_stream != nullptr) {
116+
if (p_mime_source != nullptr) {
117117
// get content type from http header
118-
mime = p_url_stream->getReplyHeader(CONTENT_TYPE);
118+
mime = p_mime_source->mime();
119119
if (mime) LOGI("mime from http request: %s", mime);
120120
}
121121
if (mime == nullptr) {
@@ -168,7 +168,7 @@ class MultiDecoder : public AudioDecoder {
168168
Vector<DecoderInfo> decoders{0};
169169
MimeDetector mime_detector;
170170
CodecNOP nop;
171-
AbstractURLStream* p_url_stream = nullptr;
171+
MimeSource* p_mime_source = nullptr;
172172
bool is_first = true;
173173
const char* selected_mime = nullptr;
174174
};

src/AudioTools/AudioCodecs/StreamingDecoder.h

Lines changed: 147 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -487,31 +487,9 @@ class MultiStreamingDecoder : public StreamingDecoder {
487487

488488
// Automatically select decoder if not already selected
489489
if (is_first) {
490-
if (actual_decoder.decoder == nullptr) {
491-
// Read some data to determine the mime type
492-
detection_buffer.resize(80);
493-
size_t bytesRead =
494-
readBytes(detection_buffer.data(), detection_buffer.size());
495-
if (bytesRead == 0) return false;
496-
497-
// Make data available again
498-
buffered_stream.setBuffer(detection_buffer.data(), bytesRead);
499-
500-
// Use the mime detector to determine format
501-
mime_detector.write(detection_buffer.data(), bytesRead);
502-
const char* mime = mime_detector.mime();
503-
504-
if (mime != nullptr) {
505-
LOGI("mime from mime_detector: %s", mime);
506-
// Select the decoder based on the determined mime type
507-
if (!selectDecoder(mime)) {
508-
LOGE("The decoder could not be found for %s", mime);
509-
return false;
510-
}
511-
} else {
512-
LOGE("Could not determine mime type");
513-
return false;
514-
}
490+
// determine the mime and select the decoder
491+
if (!selectDecoder()) {
492+
return false;
515493
}
516494

517495
// Set up buffered input stream that includes the detection data
@@ -532,7 +510,6 @@ class MultiStreamingDecoder : public StreamingDecoder {
532510
return actual_decoder.decoder->copy();
533511
}
534512

535-
536513
/**
537514
* @brief Selects the actual decoder by MIME type
538515
*
@@ -545,59 +522,73 @@ class MultiStreamingDecoder : public StreamingDecoder {
545522
*/
546523
bool selectDecoder(const char* mime) {
547524
bool result = false;
525+
526+
// Guard against null MIME type - cannot proceed without valid MIME
548527
if (mime == nullptr) return false;
549528

550-
// Do nothing if no change
529+
// Optimization: Check if the requested MIME type is already active
530+
// This avoids unnecessary decoder switching when the same format is detected
551531
if (StrView(mime).equals(actual_decoder.mime)) {
552-
is_first = false;
553-
return true;
532+
is_first = false; // Mark initialization as complete
533+
return true; // Already using the correct decoder
554534
}
555535

556-
// Close actual decoder if different
536+
// Clean shutdown of currently active decoder before switching
537+
// This ensures proper resource cleanup and state reset
557538
if (actual_decoder.decoder != nullptr) {
558539
actual_decoder.decoder->end();
559540
}
560541

561-
// Find the corresponding decoder
562-
selected_mime = nullptr;
542+
// Search through all registered decoders to find one that handles this MIME type
543+
selected_mime = nullptr; // Clear previous selection
563544
for (int j = 0; j < decoders.size(); j++) {
564545
DecoderInfo info = decoders[j];
546+
547+
// Check if this decoder supports the detected MIME type
565548
if (StrView(info.mime).equals(mime)) {
566549
LOGI("Using StreamingDecoder for %s (%s)", info.mime, mime);
550+
551+
// Switch to the matching decoder
567552
actual_decoder = info;
568553

569-
// Set up output for the selected decoder
554+
// Configure the decoder's output stream to match our output
555+
// This ensures decoded audio data flows to the correct destination
570556
if (p_print != nullptr) {
571557
actual_decoder.decoder->setOutput(*p_print);
572558
}
573559

574-
// Note: Input will be set up later with buffered stream
560+
// Note: Input stream will be configured later with the buffered stream
561+
// that preserves the data used for MIME detection
575562

576-
// Start the decoder
563+
// Initialize the selected decoder and mark it as active
577564
if (actual_decoder.decoder->begin()) {
578565
actual_decoder.is_open = true;
579566
LOGI("StreamingDecoder %s started", actual_decoder.mime);
580567
} else {
568+
// Decoder failed to start - this is a critical error
581569
LOGE("Failed to start StreamingDecoder %s", actual_decoder.mime);
582570
return false;
583571
}
584572

573+
// Successfully found and initialized a decoder
585574
result = true;
586-
selected_mime = mime;
587-
break;
575+
selected_mime = mime; // Store the MIME type that was selected
576+
break; // Stop searching once we find a match
588577
}
589578
}
579+
580+
// Mark initialization phase as complete regardless of success/failure
590581
is_first = false;
591-
return result;
582+
return result; // true if decoder was found and started, false otherwise
592583
}
593584

594585
/**
595586
* @brief Provides the MIME type of the selected decoder
596-
*
597587
* @return MIME type string of the currently active decoder, or nullptr
598588
* if no decoder is selected
599589
*/
600590
const char* mime() override {
591+
// fallback to actual decoder
601592
if (actual_decoder.decoder != nullptr) {
602593
return actual_decoder.decoder->mime();
603594
}
@@ -646,6 +637,38 @@ class MultiStreamingDecoder : public StreamingDecoder {
646637
*/
647638
MimeDetector& mimeDetector() { return mime_detector; }
648639

640+
/**
641+
* @brief Sets an external MIME source for format detection
642+
*
643+
* Provides an alternative to automatic MIME detection by allowing an external
644+
* source to provide the MIME type information. This is particularly useful
645+
* when the MIME type is already known from other sources such as:
646+
* - HTTP Content-Type headers
647+
* - File extensions
648+
* - Metadata from containers or playlists
649+
* - User-specified format preferences
650+
*
651+
* When a MIME source is set, the automatic detection process (which requires
652+
* reading and analyzing stream data) is bypassed, making the decoder
653+
* initialization more efficient and faster.
654+
*
655+
* @param mimeSource Reference to a MimeSource object that provides the
656+
* MIME type through its mime() method
657+
*
658+
* @note The MimeSource object must remain valid for the lifetime of this
659+
* MultiStreamingDecoder instance, as only a reference is stored.
660+
*
661+
* @note Setting a MIME source takes precedence over automatic detection.
662+
* To revert to automatic detection, the MIME source would need to
663+
* return nullptr from its mime() method.
664+
*
665+
* @see MimeSource interface for implementing custom MIME providers
666+
* @see selectDecoder() for how MIME type detection and selection works
667+
*
668+
* @since This feature allows integration with external metadata sources
669+
*/
670+
void setMimeSource(MimeSource& mimeSource) { p_mime_source = &mimeSource; }
671+
649672
protected:
650673
/**
651674
* @brief Simple buffered stream that prefixes buffered data before reading
@@ -811,6 +834,91 @@ class MultiStreamingDecoder : public StreamingDecoder {
811834
Vector<uint8_t> detection_buffer{0}; ///< Buffer for format detection data
812835
bool is_first = true; ///< Flag for first copy() call
813836
const char* selected_mime = nullptr; ///< MIME type that was selected
837+
MimeSource* p_mime_source =
838+
nullptr; ///< Optional MIME source for custom logic
839+
840+
/**
841+
* @brief Automatically detects MIME type and selects appropriate decoder
842+
*
843+
* This method performs automatic format detection and decoder selection when
844+
* no decoder is currently active. It supports two modes of operation:
845+
* 1. External MIME source - Uses a provided MimeSource for format information
846+
* 2. Auto-detection - Analyzes stream content to determine the audio format
847+
*
848+
* The method reads a small sample of data (80 bytes) from the input stream
849+
* for format detection, then preserves this data in a buffered stream so it
850+
* remains available to the selected decoder. This ensures no audio data is
851+
* lost during the detection process.
852+
*
853+
* @note This method is automatically called by copy() on the first invocation.
854+
* Subsequent calls will return immediately if a decoder is already selected.
855+
*
856+
* @note The detection data is preserved using BufferedPrefixStream, allowing
857+
* the selected decoder to process the complete stream including the bytes
858+
* used for format identification.
859+
*
860+
* @return true if a decoder was successfully selected and initialized, or if
861+
* a decoder was already active; false if MIME detection failed or no
862+
* matching decoder was found
863+
*
864+
* @see selectDecoder(const char* mime) for explicit decoder selection
865+
* @see setMimeSource() for providing external MIME type information
866+
* @see MimeDetector for details on automatic format detection
867+
*/
868+
bool selectDecoder() {
869+
// Only perform MIME detection and decoder selection if no decoder is active yet
870+
// This prevents re-detection on subsequent calls during the same stream
871+
if (actual_decoder.decoder == nullptr) {
872+
const char* mime = nullptr;
873+
874+
// Two methods for MIME type determination: external source or auto-detection
875+
if (p_mime_source) {
876+
// Option 1: Use externally provided MIME source (e.g., from HTTP headers)
877+
// This is more efficient as it avoids reading and analyzing stream data
878+
mime = p_mime_source->mime();
879+
} else {
880+
// Option 2: Auto-detect MIME type by analyzing stream content
881+
// This requires reading a sample of data to identify the format
882+
883+
// Allocate buffer for MIME detection sample (80 bytes is typically sufficient
884+
// for most audio format headers to be identified)
885+
detection_buffer.resize(80);
886+
size_t bytesRead = readBytes(detection_buffer.data(), detection_buffer.size());
887+
888+
// If no data is available, we cannot proceed with detection
889+
if (bytesRead == 0) return false;
890+
891+
// Preserve the detection data for the selected decoder by setting up
892+
// the buffered stream. This ensures the data used for detection
893+
// is not lost and will be available to the decoder
894+
buffered_stream.setBuffer(detection_buffer.data(), bytesRead);
895+
896+
// Feed the sample data to the MIME detector for format analysis
897+
// The detector examines file headers, magic numbers, etc.
898+
mime_detector.write(detection_buffer.data(), bytesRead);
899+
mime = mime_detector.mime();
900+
}
901+
902+
// Process the detected/provided MIME type
903+
if (mime != nullptr) {
904+
LOGI("mime from mime_detector: %s", mime);
905+
906+
// Delegate to the overloaded selectDecoder(mime) method to find
907+
// and initialize the appropriate decoder for this MIME type
908+
if (!selectDecoder(mime)) {
909+
LOGE("The decoder could not be found for %s", mime);
910+
return false; // No registered decoder can handle this format
911+
}
912+
} else {
913+
// MIME detection failed - format is unknown or unsupported
914+
LOGE("Could not determine mime type");
915+
return false;
916+
}
917+
}
918+
919+
// Success: either decoder was already selected or selection completed successfully
920+
return true;
921+
}
814922

815923
/**
816924
* @brief Reads bytes from the input stream

src/AudioTools/CoreAudio/AudioHttp/AbstractURLStream.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ namespace audio_tools {
1414
* @ingroup http
1515
* @copyright GPLv3
1616
*/
17-
class AbstractURLStream : public AudioStream {
17+
class AbstractURLStream : public AudioStream, public MimeSource {
1818
public:
1919
// executes the URL request
2020
virtual bool begin(const char* urlStr, const char* acceptMime = nullptr,
@@ -29,6 +29,11 @@ class AbstractURLStream : public AudioStream {
2929
/// Provides reply header information
3030
virtual const char* getReplyHeader(const char* header) = 0;
3131

32+
/// Provides the MIME type for the current stream
33+
const char* mime() override {
34+
return getReplyHeader(CONTENT_TYPE);
35+
}
36+
3237
// only the ICYStream supports this
3338
virtual bool setMetadataCallback(void (*fn)(MetaDataType info,
3439
const char* str, int len)) {

src/AudioTools/CoreAudio/AudioMetaData/MimeDetector.h

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,41 @@
66

77
namespace audio_tools {
88

9+
/**
10+
* @brief Abstract interface for classes that can provide MIME type information.
11+
*
12+
* This class defines a simple interface for objects that can determine and provide
13+
* MIME type strings. It serves as a base class for various MIME detection and
14+
* source identification implementations within the audio tools framework.
15+
*
16+
* Classes implementing this interface should provide logic to determine the
17+
* appropriate MIME type based on their specific context (e.g., file content,
18+
* stream headers, file extensions, etc.).
19+
*
20+
* @note This is a pure virtual interface class and cannot be instantiated directly.
21+
* @ingroup codecs
22+
* @ingroup decoder
23+
* @author Phil Schatzmann
24+
* @copyright GPLv3
25+
*/
26+
class MimeSource {
27+
public:
28+
/**
29+
* @brief Get the MIME type string.
30+
*
31+
* Pure virtual method that must be implemented by derived classes to return
32+
* the appropriate MIME type string for the current context.
33+
*
34+
* @return const char* Pointer to a null-terminated string containing the MIME type.
35+
* The string should follow standard MIME type format (e.g., "audio/mpeg").
36+
* Returns nullptr if MIME type cannot be determined.
37+
*
38+
* @note The returned pointer should remain valid for the lifetime of the object
39+
* or until the next call to this method.
40+
*/
41+
virtual const char* mime() = 0;
42+
};
43+
944
/**
1045
* @brief Logic to detemine the mime type from the content.
1146
* By default the following mime types are supported (audio/aac, audio/mpeg,
@@ -20,7 +55,7 @@ namespace audio_tools {
2055
* @copyright GPLv3
2156
*/
2257

23-
class MimeDetector {
58+
class MimeDetector : public MimeSource {
2459
public:
2560
MimeDetector() {
2661
setCheck("audio/vnd.wave", checkWAV);

0 commit comments

Comments
 (0)