diff --git a/README.md b/README.md index 433d1c2..55b0f68 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ A Twitch stream player for Windows with enhanced TLS support. - Quality selection - Windows 7+ compatibility - **Enhanced TLS Client Support** - Custom TLS implementation for better compatibility with older Windows versions +- **TSReadEX Stream Enhancement** - Optional MPEG-TS stream processing for improved compatibility and quality ## TX-Queue IPC Integration for Enhanced Performance @@ -80,6 +81,24 @@ The integration works as a fallback system: 1. Primary: WinHTTP (standard Windows HTTP library) 2. Fallback: Custom TLS client (when WinHTTP fails or on older systems) +## TSReadEX Stream Enhancement + +Tardsplaya includes optional integration with TSReadEX for advanced MPEG-TS stream processing: + +- **Stream Filtering**: Removes unwanted data like program guide information for cleaner streams +- **PID Standardization**: Remaps packet IDs to standard values for better player compatibility +- **Audio Enhancement**: Ensures consistent audio streams and handles format conversion +- **Stream Stabilization**: Processes transport streams to handle edge cases and improve reliability + +### Using TSReadEX + +1. Go to **Tools → Settings** +2. Enable **"TSReadEX Stream Enhancement"** +3. Configure options like removing program guide data and audio stabilization +4. Streaming will automatically use Transport Stream mode with TSReadEX processing + +See [TSREADEX_INTEGRATION.md](TSREADEX_INTEGRATION.md) for detailed documentation. + ## Building Requires Visual Studio 2015 or later. The project includes all necessary TLS client components and will compile them automatically. diff --git a/TESTING_TSREADEX.md b/TESTING_TSREADEX.md new file mode 100644 index 0000000..8ded420 --- /dev/null +++ b/TESTING_TSREADEX.md @@ -0,0 +1,76 @@ +# TSReadEX Integration Test + +This document describes how to test the TSReadEX integration in Tardsplaya. + +## What Was Implemented + +The TSReadEX integration provides optional MPEG-TS stream enhancement with the following capabilities: + +1. **Stream Filtering**: Removes unwanted data like EIT (program guide) packets +2. **PID Standardization**: Remaps packet IDs to standard values for better compatibility +3. **Audio Enhancement**: Ensures consistent audio streams and handles missing audio +4. **Stream Stabilization**: Processes transport streams to handle edge cases + +## How to Test + +### 1. Enable TSReadEX in Settings +1. Launch Tardsplaya +2. Go to **Tools → Settings** +3. In the "TSReadEX Stream Enhancement" section: + - ✅ Check "Enable TSReadEX processing" + - ✅ Check "Remove program guide data" (recommended) + - ✅ Check "Stabilize audio streams" (recommended) +4. Click **OK** + +### 2. Test Streaming with TSReadEX +1. Enter a Twitch channel name +2. Click **Load** to fetch qualities +3. Select a quality and click **Watch** +4. Observe the log messages: + - Should show **[TS-ROUTER]** messages instead of **[TX-QUEUE]** + - Look for **"TSReadEX processor initialized"** message + - During streaming, watch for **[TSREADEX]** processing messages + +### 3. Compare Performance +Test both modes to compare: + +**Without TSReadEX** (TX-Queue mode): +- Disable TSReadEX in settings +- Start a stream - should show **[TX-QUEUE]** messages +- Note buffering behavior and player compatibility + +**With TSReadEX** (Transport Stream mode): +- Enable TSReadEX in settings +- Start the same stream - should show **[TS-ROUTER]** messages +- Compare stream quality and player compatibility + +## Expected Behavior + +### With TSReadEX Enabled: +- Streaming mode: **Transport Stream** with TSReadEX processing +- Log messages indicate **[TS-ROUTER]** and **[TSREADEX]** activity +- Potentially cleaner stream data sent to media player +- Better compatibility with picky media players + +### With TSReadEX Disabled: +- Streaming mode: **TX-Queue IPC** (high-performance default) +- Log messages show **[TX-QUEUE]** activity +- Direct segment streaming without additional processing + +## Troubleshooting + +If TSReadEX causes issues: +1. Disable it in Settings to return to TX-Queue mode +2. Check logs for **[TSREADEX]** error messages +3. Verify media player supports transport stream input +4. Some unusual stream formats may not be compatible + +## Benefits Analysis + +TSReadEX is most beneficial when: +- Using media players that are sensitive to stream format variations +- Experiencing audio/video sync issues with certain streams +- Wanting cleaner streams with less unnecessary data +- Need standardized PID assignments for better compatibility + +The integration maintains full backward compatibility - disabling TSReadEX returns to the original high-performance TX-Queue streaming mode. \ No newline at end of file diff --git a/TSREADEX_ANSWER.md b/TSREADEX_ANSWER.md new file mode 100644 index 0000000..783cccb --- /dev/null +++ b/TSREADEX_ANSWER.md @@ -0,0 +1,62 @@ +# Can TSReadEX be used for anything? + +**Yes, TSReadEX can definitely help Tardsplaya!** + +## What TSReadEX Provides for Tardsplaya + +TSReadEX is a sophisticated MPEG-TS processing tool that enhances Tardsplaya's streaming capabilities in several important ways: + +### 1. **Better Media Player Compatibility** +- **Standardized PID assignments** work more reliably with various media players +- **Stream structure normalization** handles edge cases that might confuse players +- **Consistent packet formatting** reduces player-specific compatibility issues + +### 2. **Cleaner, More Efficient Streams** +- **Removes program guide data (EIT)** that's not needed for streaming, reducing bandwidth +- **Filters unnecessary packets** to create leaner streams +- **Removes duplicate or redundant information** from transport streams + +### 3. **Enhanced Audio Handling** +- **Ensures consistent audio streams** are always present (adds silent audio if missing) +- **Handles mono-to-stereo conversion** when needed +- **Stabilizes audio stream structure** for better synchronization + +### 4. **Stream Stabilization** +- **Handles malformed or unusual stream formats** gracefully +- **Provides consistent transport stream structure** even from varying input sources +- **Improves reliability** with streams that have formatting inconsistencies + +## How It's Integrated in Tardsplaya + +The integration is **optional and user-controlled**: + +1. **Settings Control**: Users can enable/disable TSReadEX processing in Tools → Settings +2. **Automatic Mode Switching**: When enabled, Tardsplaya uses Transport Stream mode with TSReadEX processing +3. **Fallback Capability**: If TSReadEX fails, falls back to original packet processing +4. **Performance Conscious**: Minimal overhead, only processes when enabled + +## When TSReadEX Helps Most + +TSReadEX is particularly valuable for: +- **Difficult-to-handle streams** that cause issues with standard processing +- **Media players that are sensitive** to transport stream variations +- **Users experiencing audio/video sync problems** with certain streams +- **Situations requiring maximum compatibility** across different players + +## Implementation Benefits + +- **Non-disruptive**: Doesn't change Tardsplaya's core functionality +- **Optional**: Users choose when they need enhanced processing +- **Maintains Performance**: Default TX-Queue mode remains unchanged for maximum speed +- **Comprehensive**: Includes all relevant TSReadEX features for streaming use cases + +## Conclusion + +**TSReadEX significantly enhances Tardsplaya's stream processing capabilities.** While Tardsplaya's core HLS streaming doesn't directly use MPEG-TS format, the integration provides valuable transport stream processing that: + +1. Improves compatibility with media players +2. Creates cleaner, more reliable streams +3. Handles edge cases and unusual stream formats +4. Provides professional-grade stream filtering and enhancement + +The integration proves that TSReadEX can indeed be very useful for Tardsplaya, specifically for its **transport stream processing capabilities** that enhance stream quality and player compatibility. \ No newline at end of file diff --git a/TSREADEX_INTEGRATION.md b/TSREADEX_INTEGRATION.md new file mode 100644 index 0000000..09ef691 --- /dev/null +++ b/TSREADEX_INTEGRATION.md @@ -0,0 +1,67 @@ +# TSReadEX Integration Documentation + +## Overview + +Tardsplaya now includes optional integration with TSReadEX, a sophisticated MPEG-TS processing tool that provides stream filtering, enhancement, and stabilization capabilities. + +## What is TSReadEX? + +TSReadEX (https://github.com/xtne6f/tsreadex) is a tool for MPEG-TS stream selection and stabilization. It can: + +- Filter and select specific services/programs from transport streams +- Standardize PID assignments for better player compatibility +- Ensure consistent audio streams (complement missing audio) +- Remove unwanted data like program guide information +- Handle audio stream manipulation (mono-to-stereo conversion) +- Stabilize stream structure for improved playback reliability + +## Integration Benefits + +For Tardsplaya users, TSReadEX provides: + +1. **Improved Stream Compatibility**: Standardized PID assignments work better with various media players +2. **Cleaner Streams**: Removes unnecessary program guide data to reduce bandwidth +3. **Better Audio Handling**: Ensures consistent audio streams for reliable playback +4. **Enhanced Stability**: Stream processing that handles edge cases in transport stream format + +## How to Enable + +1. Go to **Tools → Settings** +2. In the "TSReadEX Stream Enhancement" section: + - Check **"Enable TSReadEX processing"** + - Optionally configure: + - **"Remove program guide data"** - Removes EIT data for cleaner streams (recommended) + - **"Stabilize audio streams"** - Ensures consistent audio presence (recommended) +3. Click **OK** to save settings + +## When TSReadEX is Active + +- Streaming mode automatically switches to **Transport Stream** mode +- TSReadEX processes the stream before sending to media player +- Log messages will show **[TS_ROUTER]** and **[TSREADEX]** entries +- Stream filtering statistics are logged periodically + +## Performance Impact + +- Minimal CPU overhead for stream processing +- Slightly increased memory usage for buffering +- May reduce overall bandwidth due to filtered data +- Transport Stream mode has different buffering characteristics than TX-Queue mode + +## Troubleshooting + +If you experience issues with TSReadEX enabled: + +1. **Disable TSReadEX** in settings to return to standard TX-Queue mode +2. Check logs for **[TSREADEX]** error messages +3. Verify your media player supports transport stream input +4. Some very unusual stream formats may not be compatible + +## Technical Details + +- TSReadEX processes streams in the Transport Stream pipeline +- Works as optional post-processing filter +- Maintains compatibility with existing streaming infrastructure +- Automatically falls back to original packets if processing fails + +The integration provides these capabilities without disrupting Tardsplaya's core functionality, allowing users to enable enhanced stream processing when needed. \ No newline at end of file diff --git a/Tardsplaya.cpp b/Tardsplaya.cpp index 6b121d8..53bf281 100644 --- a/Tardsplaya.cpp +++ b/Tardsplaya.cpp @@ -134,6 +134,11 @@ bool g_logAutoScroll = true; bool g_minimizeToTray = false; bool g_logToFile = false; // Enable logging to debug.log file +// TSReadEX settings +bool g_enableTSReadEX = false; // Enable TSReadEX stream processing +bool g_tsreadexRemoveEIT = true; // Remove program guide data +bool g_tsreadexStabilizeAudio = true; // Stabilize audio streams + // Tray icon support @@ -198,6 +203,11 @@ void LoadSettings() { // Load verbose debug setting g_verboseDebug = GetPrivateProfileIntW(L"Settings", L"VerboseDebug", 0, iniPath.c_str()) != 0; + + // Load TSReadEX settings + g_enableTSReadEX = GetPrivateProfileIntW(L"Settings", L"EnableTSReadEX", 0, iniPath.c_str()) != 0; + g_tsreadexRemoveEIT = GetPrivateProfileIntW(L"Settings", L"TSReadEXRemoveEIT", 1, iniPath.c_str()) != 0; + g_tsreadexStabilizeAudio = GetPrivateProfileIntW(L"Settings", L"TSReadEXStabilizeAudio", 1, iniPath.c_str()) != 0; } void SaveSettings() { @@ -225,6 +235,11 @@ void SaveSettings() { // Save verbose debug setting WritePrivateProfileStringW(L"Settings", L"VerboseDebug", g_verboseDebug ? L"1" : L"0", iniPath.c_str()); + + // Save TSReadEX settings + WritePrivateProfileStringW(L"Settings", L"EnableTSReadEX", g_enableTSReadEX ? L"1" : L"0", iniPath.c_str()); + WritePrivateProfileStringW(L"Settings", L"TSReadEXRemoveEIT", g_tsreadexRemoveEIT ? L"1" : L"0", iniPath.c_str()); + WritePrivateProfileStringW(L"Settings", L"TSReadEXStabilizeAudio", g_tsreadexStabilizeAudio ? L"1" : L"0", iniPath.c_str()); } void AddLog(const std::wstring& msg) { @@ -922,10 +937,16 @@ void WatchStream(StreamTab& tab, size_t tabIndex) { AddDebugLog(L"WatchStream: Creating stream thread for tab " + std::to_wstring(tabIndex) + L", PlayerPath=" + g_playerPath + L", URL=" + url); - // TX-Queue IPC Mode is now the default streaming mode - StreamingMode mode = StreamingMode::TX_QUEUE_IPC; + // Choose streaming mode based on settings + StreamingMode mode = StreamingMode::TX_QUEUE_IPC; // Default high-performance mode - AddLog(L"[TX-QUEUE] Starting TX-Queue IPC streaming for " + tab.channel + L" (" + standardQuality + L")"); + if (g_enableTSReadEX) { + // Use transport stream mode for TSReadEX processing + mode = StreamingMode::TRANSPORT_STREAM; + AddLog(L"[TS-ROUTER] Starting Transport Stream with TSReadEX for " + tab.channel + L" (" + standardQuality + L")"); + } else { + AddLog(L"[TX-QUEUE] Starting TX-Queue IPC streaming for " + tab.channel + L" (" + standardQuality + L")"); + } // Start the buffering thread tab.streamThread = StartStreamThread( @@ -1278,10 +1299,27 @@ INT_PTR CALLBACK SettingsDlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM CheckDlgButton(hDlg, IDC_MINIMIZETOTRAY, g_minimizeToTray ? BST_CHECKED : BST_UNCHECKED); CheckDlgButton(hDlg, IDC_VERBOSE_DEBUG, g_verboseDebug ? BST_CHECKED : BST_UNCHECKED); CheckDlgButton(hDlg, IDC_LOG_TO_FILE, g_logToFile ? BST_CHECKED : BST_UNCHECKED); + + // Initialize TSReadEX settings + CheckDlgButton(hDlg, IDC_ENABLE_TSREADEX, g_enableTSReadEX ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hDlg, IDC_TSREADEX_REMOVE_EIT, g_tsreadexRemoveEIT ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hDlg, IDC_TSREADEX_STABILIZE_AUDIO, g_tsreadexStabilizeAudio ? BST_CHECKED : BST_UNCHECKED); + + // Enable/disable TSReadEX sub-options based on main setting + EnableWindow(GetDlgItem(hDlg, IDC_TSREADEX_REMOVE_EIT), g_enableTSReadEX); + EnableWindow(GetDlgItem(hDlg, IDC_TSREADEX_STABILIZE_AUDIO), g_enableTSReadEX); return TRUE; case WM_COMMAND: switch (LOWORD(wParam)) { + case IDC_ENABLE_TSREADEX: + // Enable/disable TSReadEX sub-options when main checkbox changes + { + bool enabled = IsDlgButtonChecked(hDlg, IDC_ENABLE_TSREADEX) == BST_CHECKED; + EnableWindow(GetDlgItem(hDlg, IDC_TSREADEX_REMOVE_EIT), enabled); + EnableWindow(GetDlgItem(hDlg, IDC_TSREADEX_STABILIZE_AUDIO), enabled); + } + return TRUE; case IDC_BROWSE_PLAYER: { OPENFILENAMEW ofn = { sizeof(ofn) }; wchar_t szFile[MAX_PATH] = { 0 }; @@ -1317,6 +1355,11 @@ INT_PTR CALLBACK SettingsDlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM g_verboseDebug = IsDlgButtonChecked(hDlg, IDC_VERBOSE_DEBUG) == BST_CHECKED; g_logToFile = IsDlgButtonChecked(hDlg, IDC_LOG_TO_FILE) == BST_CHECKED; + // Save TSReadEX settings + g_enableTSReadEX = IsDlgButtonChecked(hDlg, IDC_ENABLE_TSREADEX) == BST_CHECKED; + g_tsreadexRemoveEIT = IsDlgButtonChecked(hDlg, IDC_TSREADEX_REMOVE_EIT) == BST_CHECKED; + g_tsreadexStabilizeAudio = IsDlgButtonChecked(hDlg, IDC_TSREADEX_STABILIZE_AUDIO) == BST_CHECKED; + // Save settings to INI file SaveSettings(); diff --git a/Tardsplaya.vcxproj b/Tardsplaya.vcxproj index 9114474..93b0080 100644 --- a/Tardsplaya.vcxproj +++ b/Tardsplaya.vcxproj @@ -117,6 +117,13 @@ + + + + + + + @@ -135,6 +142,13 @@ + + + + + + + diff --git a/resource.h b/resource.h index a9a119d..8c493ce 100644 --- a/resource.h +++ b/resource.h @@ -26,6 +26,9 @@ #define IDC_BROWSE_PLAYER 1303 #define IDC_VERBOSE_DEBUG 1304 #define IDC_LOG_TO_FILE 1305 +#define IDC_ENABLE_TSREADEX 1306 +#define IDC_TSREADEX_REMOVE_EIT 1307 +#define IDC_TSREADEX_STABILIZE_AUDIO 1308 #define IDC_LOAD 1101 #define IDC_QUALITIES 1102 #define IDC_WATCH 1103 diff --git a/resource.rc b/resource.rc index 1a6780c..697f5a1 100644 --- a/resource.rc +++ b/resource.rc @@ -24,7 +24,7 @@ MENUITEM "&Settings...", IDM_SETTINGS END END -IDD_SETTINGS DIALOG 0, 0, 320, 180 +IDD_SETTINGS DIALOG 0, 0, 320, 220 STYLE DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "Settings" FONT 8, "MS Sans Serif" @@ -40,6 +40,11 @@ BEGIN CONTROL "Verbose debug", IDC_VERBOSE_DEBUG, "Button", BS_AUTOCHECKBOX | WS_TABSTOP, 12, 85, 70, 10 CONTROL "Log to file (debug.log)", IDC_LOG_TO_FILE, "Button", BS_AUTOCHECKBOX | WS_TABSTOP, 12, 105, 100, 10 - DEFPUSHBUTTON "OK", IDOK, 200, 150, 50, 14 - PUSHBUTTON "Cancel", IDCANCEL, 260, 150, 50, 14 + GROUPBOX "TSReadEX Stream Enhancement", IDC_STATIC, 12, 125, 285, 55 + CONTROL "Enable TSReadEX processing", IDC_ENABLE_TSREADEX, "Button", BS_AUTOCHECKBOX | WS_TABSTOP, 22, 140, 120, 10 + CONTROL "Remove program guide data", IDC_TSREADEX_REMOVE_EIT, "Button", BS_AUTOCHECKBOX | WS_TABSTOP, 22, 155, 120, 10 + CONTROL "Stabilize audio streams", IDC_TSREADEX_STABILIZE_AUDIO, "Button", BS_AUTOCHECKBOX | WS_TABSTOP, 160, 155, 100, 10 + + DEFPUSHBUTTON "OK", IDOK, 200, 190, 50, 14 + PUSHBUTTON "Cancel", IDCANCEL, 260, 190, 50, 14 END \ No newline at end of file diff --git a/stream_thread.cpp b/stream_thread.cpp index 33706d4..fe7bd5b 100644 --- a/stream_thread.cpp +++ b/stream_thread.cpp @@ -4,6 +4,11 @@ #include "stream_resource_manager.h" #include "tx_queue_ipc.h" +// External global variables for settings +extern bool g_enableTSReadEX; +extern bool g_tsreadexRemoveEIT; +extern bool g_tsreadexStabilizeAudio; + std::thread StartStreamThread( const std::wstring& player_path, const std::wstring& playlist_url, @@ -233,6 +238,12 @@ std::thread StartTransportStreamThread( config.playlist_refresh_interval = std::chrono::milliseconds(500); // Check every 500ms config.skip_old_segments = true; + // Configure TSReadEX based on global settings + config.enable_tsreadex = g_enableTSReadEX; + config.tsreadex_remove_eit = g_tsreadexRemoveEIT; + config.tsreadex_stabilize_audio = g_tsreadexStabilizeAudio; + config.tsreadex_standardize_pids = true; // Always standardize PIDs for better compatibility + if (log_callback) { log_callback(L"[TS_MODE] Starting TSDuck transport stream routing"); log_callback(L"[TS_MODE] Buffer: " + std::to_wstring(buffer_packets) + L" packets (~" + diff --git a/tsduck_transport_router.cpp b/tsduck_transport_router.cpp index 4dcfcd6..cc75dac 100644 --- a/tsduck_transport_router.cpp +++ b/tsduck_transport_router.cpp @@ -3,6 +3,7 @@ #include "playlist_parser.h" #include "tsduck_hls_wrapper.h" #include "stream_resource_manager.h" +#include "tsreadex_wrapper.h" #define NOMINMAX #include #include @@ -679,6 +680,7 @@ TransportStreamRouter::TransportStreamRouter() { ts_buffer_ = std::make_unique(buffer_size); hls_converter_ = std::make_unique(); + tsreadex_processor_ = std::make_unique(); } TransportStreamRouter::~TransportStreamRouter() { @@ -702,11 +704,37 @@ bool TransportStreamRouter::StartRouting(const std::wstring& hls_playlist_url, ts_buffer_->Reset(); // This will clear packets and reset producer_active ts_buffer_->SetLowLatencyMode(config.low_latency_mode); // Configure buffer for latency mode + // Initialize TSReadEX processor if enabled + if (config.enable_tsreadex) { + tardsplaya::TSReadEXProcessor::Config tsreadex_config; + tsreadex_config.enabled = true; + tsreadex_config.program_number = -1; // Select first program + tsreadex_config.remove_eit = config.tsreadex_remove_eit; + tsreadex_config.stabilize_audio = config.tsreadex_stabilize_audio; + tsreadex_config.standardize_pids = config.tsreadex_standardize_pids; + tsreadex_config.complement_missing_audio = true; + + if (!tsreadex_processor_->Initialize(tsreadex_config)) { + if (log_callback_) { + log_callback_(L"[TS_ROUTER] Warning: Failed to initialize TSReadEX processor, continuing without it"); + } + } else { + if (log_callback_) { + log_callback_(L"[TS_ROUTER] TSReadEX processor initialized for stream enhancement"); + } + } + } + if (log_callback_) { log_callback_(L"[TS_ROUTER] Starting TSDuck-inspired transport stream routing"); log_callback_(L"[TS_ROUTER] Player: " + config.player_path); log_callback_(L"[TS_ROUTER] Buffer size: " + std::to_wstring(config.buffer_size_packets) + L" packets"); + if (config.enable_tsreadex) { + log_callback_(L"[TS_ROUTER] TSReadEX stream enhancement: " + + std::wstring(tsreadex_processor_->IsEnabled() ? L"ENABLED" : L"FAILED")); + } + if (config.low_latency_mode) { log_callback_(L"[LOW_LATENCY] Mode enabled - targeting minimal stream delay"); log_callback_(L"[LOW_LATENCY] Max segments: " + std::to_wstring(config.max_segments_to_buffer) + @@ -1176,11 +1204,55 @@ void TransportStreamRouter::TSRouterThread(std::atomic& cancel_token) { } } - if (!SendTSPacketToPlayer(player_stdin, packet)) { - if (log_callback_) { - log_callback_(L"[TS_ROUTER] Failed to send TS packet to player - pipe may be broken"); + // Apply TSReadEX processing if enabled + if (current_config_.enable_tsreadex && tsreadex_processor_->IsEnabled()) { + std::vector input_data(packet.data, packet.data + TS_PACKET_SIZE); + std::vector processed_data; + + if (tsreadex_processor_->ProcessChunk(input_data, processed_data)) { + // TSReadEX may return multiple or no packets - send all processed packets + size_t processed_packets = processed_data.size() / TS_PACKET_SIZE; + + for (size_t i = 0; i < processed_packets; ++i) { + TSPacket processed_packet; + memcpy(processed_packet.data, processed_data.data() + (i * TS_PACKET_SIZE), TS_PACKET_SIZE); + processed_packet.ParseHeader(); // Update packet metadata + + if (!SendTSPacketToPlayer(player_stdin, processed_packet)) { + if (log_callback_) { + log_callback_(L"[TS_ROUTER] Failed to send processed TS packet to player"); + } + goto cleanup_and_exit; + } + } + + if (processed_packets != 1) { + // Log when TSReadEX filtering changes packet count + static int filter_log_count = 0; + if (filter_log_count < 5) { // Limit logging to prevent spam + if (log_callback_) { + log_callback_(L"[TSREADEX] Filtered 1 packet -> " + std::to_wstring(processed_packets) + L" packets"); + } + filter_log_count++; + } + } + } else { + // TSReadEX processing failed, fall back to original packet + if (!SendTSPacketToPlayer(player_stdin, packet)) { + if (log_callback_) { + log_callback_(L"[TS_ROUTER] Failed to send TS packet to player - pipe may be broken"); + } + goto cleanup_and_exit; + } + } + } else { + // Normal processing without TSReadEX + if (!SendTSPacketToPlayer(player_stdin, packet)) { + if (log_callback_) { + log_callback_(L"[TS_ROUTER] Failed to send TS packet to player - pipe may be broken"); + } + goto cleanup_and_exit; } - goto cleanup_and_exit; } packets_sent++; last_packet_time = std::chrono::steady_clock::now(); diff --git a/tsduck_transport_router.h b/tsduck_transport_router.h index f5a4e75..e22fa3b 100644 --- a/tsduck_transport_router.h +++ b/tsduck_transport_router.h @@ -16,6 +16,9 @@ #define NOMINMAX #include +// TSReadEX integration +#include "tsreadex_wrapper.h" + namespace tsduck_transport { // Transport Stream packet size (MPEG-TS standard) @@ -173,6 +176,12 @@ namespace tsduck_transport { size_t max_segments_to_buffer = 2; // Only buffer latest N segments for live edge std::chrono::milliseconds playlist_refresh_interval{500}; // Check for new segments every 500ms bool skip_old_segments = true; // Skip older segments when catching up + + // TSReadEX stream processing options + bool enable_tsreadex = false; // Enable TSReadEX post-processing + bool tsreadex_remove_eit = true; // Remove program guide data for cleaner streams + bool tsreadex_stabilize_audio = true; // Ensure consistent audio streams + bool tsreadex_standardize_pids = true; // Remap PIDs to standard values }; // Start routing HLS stream to media player via transport stream @@ -217,6 +226,7 @@ namespace tsduck_transport { private: std::unique_ptr ts_buffer_; std::unique_ptr hls_converter_; + std::unique_ptr tsreadex_processor_; // TSReadEX integration std::atomic routing_active_{false}; std::thread hls_fetcher_thread_; std::thread ts_router_thread_; diff --git a/tsreadex/License.txt b/tsreadex/License.txt new file mode 100644 index 0000000..a50b9f9 --- /dev/null +++ b/tsreadex/License.txt @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2021 xtne6f + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/tsreadex/Readme.txt b/tsreadex/Readme.txt new file mode 100644 index 0000000..e4d3f80 --- /dev/null +++ b/tsreadex/Readme.txt @@ -0,0 +1,135 @@ +tsreadex - MPEG-TSのストリーム選択と安定化のためのツール + +使用法: + +tsreadex [-z ignored][-s seek][-l limit][-t timeout][-m mode][-x pids][-n prog_num_or_index][-a aud1][-b aud2][-c cap][-u sup][-r trace][-d flags] src + +-z ignored + 必ず無視されるパラメータ(プロセス識別用など)。 + +-s seek (bytes), default=0 + ファイルの初期シーク量。0未満のときはファイル末尾から-(seek+1)だけ前方にシークする。 + 入力がパイプ系のときは0でなければならない。 + +-l limit (kbytes/second), 0<=range<=32768, default=0 + 入力の最大読み込み速度。0のとき無制限。 + "-n"オプションでサービスID指定する場合などで、もしそのサービスが見つからない場合には出力するものがないためストレージの + 最大負荷で読み込みが行われてしまうことになるが、このオプションで制限できる。 + +-t timeout (seconds), 0<=range<=600, default=0 + この秒数以上のあいだ出力が全くないときタイムアウトとして終了する。 + +-m mode, range=0 or 1 or 2, default=0 + タイムアウトの方式。 + 0: 通常読み込み。 + timeoutが0のときは入力終了(ファイル終端など)までタイムアウトせず、入力終了後すぐに終了する。 + timeoutが0でないときは、ファイル終端に達した後ファイルに追記がないか待ってから終了する。 + 入力がパイプ系のときはtimeout=0でなければならない。 + 1: 容量確保ファイルの読み込み。 + mode=0のときと基本的に同じだが、ファイル終端または内容が正しいMPEG-TSでなくなった部分を終端とする。 + 入力はパイプ系であってはならない。 + 2: 非ブロッキングパイプ読み込み。 + 入力終了後すぐに終了する。入力が滞った場合にも(タイムアウトにより)終了する。 + 入力はパイプ系でなければならない。timeoutは0であってはならない。 + +-x pids, default="" + 取りのぞくTSパケットのPIDを'/'区切りで指定。 + +-n prog_num_or_index, -256<=range<=65535, default=0 + 特定サービスのみを選択して出力するフィルタを有効にする。 + サービスID(1以上)かPAT(Program Association Table)上の並び順(先頭を-1として-1,-2,..)を指定する。 + PIDが0x0030未満か以下の特定ストリームのみ出力されるようになり、PIDは以下のように固定される。 + - 映像: PID=0x0100 + - 第1音声(AACかMP2のみ※): PID=0x0110 + - 第2音声(AACかMP2のみ※): PID=0x0111 + - ARIB字幕: PID=0x0130 + - ARIB文字スーパー: PID=0x0138 + - PMT(Program Map Table): PID=0x01f0 + - PCR(Program Clock Reference): PID=0x01ff (ただし上記ストリームに重畳されている場合はそのPID) + ※MP2はSTD-B1準拠の音声のみ形式のものを想定。 + このフィルタが有効でないとき"-a"、"-b"、"-c"、"-u"オプションは無視される。 + +-a aud1, range=0 or 1 [+4] [+8], default=0 + 第1音声をそのままか、補完するか。 + 1のとき、ストリームが存在しなければPMTの項目を補って無音のAACストリームを挿入する。 + +4のとき、モノラルであればステレオにする(AACのみ)。 + +8のとき、デュアルモノ(ARIB STD-B32)を2つのモノラル音声に分離し、右音声を第2音声として扱う(AACのみ)。 + +-b aud2, range=0 or 1 or 2 or 3 [+4], default=0 + 第2音声をそのままか、補完するか、削除するか、第1音声をコピーするか。 + 1のとき、ストリームが存在しなければPMTの項目を補って無音のAACストリームを挿入する。 + 3のとき、ストリームが存在しなければ第1音声をコピーする。 + +4のとき、モノラルであればステレオにする(AACのみ)。 + +-c cap, range=0 or 1 or 2 [+4], default=0 + ARIB字幕をそのままか、補完するか、削除するか。 + 1のとき、ストリームが存在しなければPMTの項目を補う。 + +4のとき、実際の字幕データが現れない場合に5秒ごとに非表示の適当なデータを挿入する。これはffmpeg 6.0時点で字幕データが + 存在しないときにプロセスが終了しなくなる場合があるのを回避するもの。 + +-u sup, range=0 or 1 or 2 [+4], default=0 + ARIB文字スーパーをそのままか、補完するか、削除するか。 + 1のとき、ストリームが存在しなければPMTの項目を補う。 + +4のとき、実際の文字スーパーのデータが現れない場合に5秒ごとに非表示の適当なデータを挿入する("-c"オプションと同様)。 + +-r trace, default="" + ストリームについての情報をUTF-8文字列で出力するファイル名、または"-"で標準出力。 + "-"のとき本来の出力(TSパケット)は抑制される。 + 今のところ以下のような情報を出力する。 + - 初めて現れたPCRのタイムスタンプ + pcrpid=0x{4桁PID};pcr={10桁タイムスタンプ} + - エスケープしたARIB字幕のデータとそのPTS + pts={10桁タイムスタンプ};pcrrel=[+-]{7桁PCRとの差}[;text={可読な文字列の位置},..];b24caption[0-8]={改行までデータ} + # [0-8]はそれぞれ字幕管理と字幕文第1~8言語 + # 字幕データはARIB STD-B24のデータグループ(data_group)構造を原則 %{2桁HEX} でエスケープして表現したもの + # ただし、data_group_sizeおよびCRC_16フィールドは取り除かれる + # 制御文字をのぞくUTF-8として表現可能な部分はエスケープしないことがある + # C1制御文字 U+0080~U+009F はキャレット記法 %^@~%^_ によりエスケープすることがある + # %={ および %=} の括弧の対応は、%=} までのバイト数を %={ の位置にビッグエンディアン24bitとして置き換えたものと等価 + # %+{ および %+} の括弧の区間はBase64エンコードされている(DRCSデータに使用) + # 字幕本文はARIB STD-B24のUCSの規定に沿ってできるだけUTF-8に変換される + - エスケープしたARIB文字スーパーのデータ + pts={10桁タイムスタンプ};pcrrel=+0000000[;text={可読な文字列の位置},..];b24superimpose[0-8]={改行までデータ} + # PTSにはPCRタイムスタンプが使われる + # ほかARIB字幕と同様 + - エスケープ処理に失敗したとき + pts={10桁タイムスタンプ};pcrrel=[+-]{7桁PCRとの差};b24captionerr={改行まで失敗理由} + pts={10桁タイムスタンプ};pcrrel=+0000000;b24superimposeerr={改行まで失敗理由} + +-d flags, range=0 or 1 [+2] [+4] [+8], default=0 + ARIB字幕/文字スーパーを https://github.com/monyone/aribb24.js が解釈できるID3 timed-metadataに変換する。 + 変換元のストリームは削除される。 + +2のとき、不明な"private data"ストリームをARIB文字スーパーとして扱う。ffmpegを経由した入力など記述子が正しく転送されて + いない入力に対処するもので、普通は使わない。 + +4のとき、変換後のストリームに規格外の5バイトのデータを追加する。これはffmpeg 4.4時点のlibavformat/mpegts.cに存在する + バグを打ち消すためのもので、node-arib-subtitle-timedmetadaterの手法に基づく。出力をffmpegなどに渡す場合にのみ使用する + こと。このバグはffmpeg 6.1で修正された。 + +8のとき、変換後のストリームのPTS(Presentation Timestamp)が単調増加となるように調整する。ffmpeg 4.4時点においてPTSを + DTS(Decoding Timestamp)とみなしタイムスタンプが遡るとエラーとなるのを防ぐもの。ARIB字幕/文字スーパーの両方が存在する場 + 合で、出力をffmpegなどに渡す場合に使用する。 + +src + 入力ファイル名、または"-"で標準入力 + +説明: + +このツールは大まかに3段のフィルター構造になっている。入力された188か192か204バイトのMPEG-TSパケットを同期語(0x47)で同期 +し、最初に"-x"オプションで指定されたパケットを取りのぞく(1段)。つぎに、"-n"オプションが0でないときは特定サービスを選択し +てストリームの補完などを行う(2段)。最後に"-d"オプションによる変換を行い(3段)、188バイトのTSパケットとして標準出力する。 +たとえば、ARIB仕様のTSパケットからEIT(番組表データ)を取り除き、PATの先頭サービスのみ出力し、第2音声と字幕がつねに存在す +るようにして文字スーパーを削除し、ARIB字幕をID3 timed-metadataに変換するときは以下のようになる。 +> tsreadex -x 18/38/39 -n -1 -b 1 -c 1 -u 2 -d 1 src.m2t > dest.m2t + +その他: + +ライセンスはMITとする。 + +"-n"オプションはおもにffmpegが動的な副音声追加などを扱えないのをなんとかするためのもの。ffmpegなどの改善により不要になる +のが理想だと思う。 + +"-n"オプションはtsukumi( https://github.com/tsukumijima )氏のSlack上でのアイデアをもとに設計した。 + +"-d"オプションの実装にあたり https://github.com/monyone/node-arib-subtitle-timedmetadater を参考にした。 + +デュアルモノ分離などの実装にあたり https://github.com/monyone/node-aac-dualmono-splitter を参考にした。 +デュアルモノ分離、ステレオ化は典型的な形式(AAC-LC,48/44.1/32kHz)のみ対応。ほかの形式に対しては原則、無変換になる(はず)。 diff --git a/tsreadex/aac.cpp b/tsreadex/aac.cpp new file mode 100644 index 0000000..ff08cf1 --- /dev/null +++ b/tsreadex/aac.cpp @@ -0,0 +1,813 @@ +#include "aac.hpp" +#include "huffman.hpp" +#include "util.hpp" +#include +#include + +namespace +{ +const int ONLY_LONG_SEQUENCE = 0; +const int LONG_START_SEQUENCE = 1; +const int EIGHT_SHORT_SEQUENCE = 2; +const int LONG_STOP_SEQUENCE = 3; + +const uint16_t SWB_OFFSET_LONG_WINDOW_48KHZ[64] = +{ + 0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 48, 56, 64, 72, 80, 88, 96, 108, 120, 132, 144, 160, 176, 196, + 216, 240, 264, 292, 320, 352, 384, 416, 448, 480, 512, 544, 576, 608, 640, 672, 704, 736, 768, 800, 832, 864, 896, 928, 1024, + // padding + 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, +}; + +const uint16_t SWB_OFFSET_LONG_WINDOW_32KHZ[64] = +{ + 0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 48, 56, 64, 72, 80, 88, 96, 108, 120, 132, 144, 160, 176, 196, 216, + 240, 264, 292, 320, 352, 384, 416, 448, 480, 512, 544, 576, 608, 640, 672, 704, 736, 768, 800, 832, 864, 896, 928, 960, 992, 1024, + // padding + 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, +}; + +const uint16_t SWB_OFFSET_SHORT_WINDOW_48KHZ[16] = +{ + 0, 4, 8, 12, 16, 20, 28, 36, 44, 56, 68, 80, 96, 112, 128, + // padding + 128, +}; + +const int PRED_SFB_MAX_48KHZ = 40; + +const int ZERO_HCB = 0; +const int FIRST_PAIR_HCB = 5; +const int ESC_HCB = 11; + +const int ESC_FLAG = 16; + +const int EXT_DYNAMIC_RANGE = 11; +const int EXT_SBR_DATA = 13; +const int EXT_SBR_DATA_CRC = 14; + +const int ID_SCE = 0; +const int ID_CPE = 1; +const int ID_DSE = 4; +const int ID_PCE = 5; +const int ID_FIL = 6; +const int ID_END = 7; + +const size_t EXTRA_WORKSPACE_BYTES = 16; + +inline bool CheckOverrun(size_t lenBytes, size_t pos) +{ + assert(pos <= lenBytes * 8); + return pos <= lenBytes * 8; +} + +void ByteAlignment(size_t &pos) +{ + pos = (pos + 7) / 8 * 8; +} + +bool SingleChannelElement(const uint8_t *aac, size_t lenBytes, size_t &pos, bool is32khz) +{ + pos += 4; + + // individual_channel_stream(0) + pos += 8; + + // ics_info + ++pos; + int windowSequence = read_bits(aac, pos, 2); + ++pos; + int maxSfb; + int numWindowGroups = 1; + int windowGroupLength[8]; + windowGroupLength[0] = 1; + if (windowSequence == EIGHT_SHORT_SEQUENCE) { + maxSfb = read_bits(aac, pos, 4); + int scaleFactorGrouping = read_bits(aac, pos, 7); + for (int i = 6; i >= 0; --i) { + if ((scaleFactorGrouping >> i) & 1) { + ++windowGroupLength[numWindowGroups - 1]; + } + else { + windowGroupLength[numWindowGroups++] = 1; + } + } + } + else { + maxSfb = read_bits(aac, pos, 6); + bool predictorDataPresent = read_bool(aac, pos); + if (predictorDataPresent) { + bool predictorReset = read_bool(aac, pos); + if (predictorReset) { + pos += 5; + } + pos += std::min(maxSfb, PRED_SFB_MAX_48KHZ); + } + } + + // Determine sect_sfb_offset + int numWindows; + int sectSfbOffset[8][64]; + if (windowSequence == EIGHT_SHORT_SEQUENCE) { + numWindows = 8; + for (int g = 0; g < numWindowGroups; ++g) { + int offset = 0; + for (int i = 0; i < maxSfb; ++i) { + sectSfbOffset[g][i] = offset; + offset += (SWB_OFFSET_SHORT_WINDOW_48KHZ[i + 1] - SWB_OFFSET_SHORT_WINDOW_48KHZ[i]) * windowGroupLength[g]; + } + sectSfbOffset[g][maxSfb] = offset; + } + } + else { + numWindows = 1; + if (is32khz) { + std::copy(SWB_OFFSET_LONG_WINDOW_32KHZ, SWB_OFFSET_LONG_WINDOW_32KHZ + maxSfb + 1, sectSfbOffset[0]); + } + else { + std::copy(SWB_OFFSET_LONG_WINDOW_48KHZ, SWB_OFFSET_LONG_WINDOW_48KHZ + maxSfb + 1, sectSfbOffset[0]); + } + } + + // section_data + int numSec[8]; + int sectCb[8][64]; + int sectEnd[8][64]; + int sfbCb[8][64]; + for (int g = 0; g < numWindowGroups; ++g) { + int sectLenIncrBits = windowSequence == EIGHT_SHORT_SEQUENCE ? 3 : 5; + int sectEscVal = windowSequence == EIGHT_SHORT_SEQUENCE ? 7 : 31; + int i = 0; + for (int k = 0; k < maxSfb; ++i) { + if (!CheckOverrun(lenBytes, pos)) { + return false; + } + sectCb[g][i] = read_bits(aac, pos, 4); + int sectLen = 0; + for (;;) { + if (!CheckOverrun(lenBytes, pos)) { + return false; + } + int sectLenIncr = read_bits(aac, pos, sectLenIncrBits); + sectLen += sectLenIncr; + if (k + sectLen > maxSfb) { + assert(false); + return false; + } + if (sectLenIncr != sectEscVal) { + break; + } + } + for (int sfb = k; sfb < k + sectLen; ++sfb) { + sfbCb[g][sfb] = sectCb[g][i]; + } + k += sectLen; + sectEnd[g][i] = k; + } + numSec[g] = i; + } + + // scale_factor_data (ISO/IEC 14496-3 extended) + bool noisePcmFlag = true; + for (int g = 0; g < numWindowGroups; ++g) { + for (int sfb = 0; sfb < maxSfb; ++sfb) { + if (sfbCb[g][sfb] != ZERO_HCB) { + if (!CheckOverrun(lenBytes, pos)) { + return false; + } + if (sfbCb[g][sfb] == 13 && noisePcmFlag) { + noisePcmFlag = false; + pos += 9; + } + else { + Huffman::DecodeScalefactorBits(aac, pos); + } + } + } + } + + if (!CheckOverrun(lenBytes, pos)) { + return false; + } + bool pulseDataPresent = read_bool(aac, pos); + if (pulseDataPresent) { + // pulse_data + int numberPulse = read_bits(aac, pos, 2); + pos += 6 + 9 * (numberPulse + 1); + } + + if (!CheckOverrun(lenBytes, pos)) { + return false; + } + bool tnsDataPresent = read_bool(aac, pos); + if (tnsDataPresent) { + // tns_data + int nFiltBits = windowSequence == EIGHT_SHORT_SEQUENCE ? 1 : 2; + int lengthBits = windowSequence == EIGHT_SHORT_SEQUENCE ? 4 : 6; + int orderBits = windowSequence == EIGHT_SHORT_SEQUENCE ? 3 : 5; + for (int w = 0; w < numWindows; ++w) { + if (!CheckOverrun(lenBytes, pos)) { + return false; + } + int nFilt = read_bits(aac, pos, nFiltBits); + int coefRes = 0; + if (nFilt) { + coefRes = read_bits(aac, pos, 1); + } + for (int f = 0; f < nFilt; ++f) { + pos += lengthBits; + if (!CheckOverrun(lenBytes, pos)) { + return false; + } + int order = read_bits(aac, pos, orderBits); + if (order) { + ++pos; + int coefCompress = read_bits(aac, pos, 1); + pos += (3 + coefRes - coefCompress) * order; + } + } + } + } + + if (!CheckOverrun(lenBytes, pos)) { + return false; + } + bool gainControlDataPresent = read_bool(aac, pos); + if (gainControlDataPresent) { + // gain_control_data + int maxBand = read_bits(aac, pos, 2); + int wdCount = ONLY_LONG_SEQUENCE ? 1 : EIGHT_SHORT_SEQUENCE ? 8 : 2; + for (int bd = 1; bd <= maxBand; ++bd) { + for (int wd = 0; wd < wdCount; ++wd) { + if (!CheckOverrun(lenBytes, pos)) { + return false; + } + int adjustNum = read_bits(aac, pos, 3); + int adjustBits = ONLY_LONG_SEQUENCE ? 9 : + EIGHT_SHORT_SEQUENCE ? 6 : + LONG_START_SEQUENCE ? (wd == 0 ? 8 : 6) : (wd == 0 ? 8 : 9); + pos += adjustBits * adjustNum; + } + } + } + + if (!CheckOverrun(lenBytes, pos)) { + return false; + } + // spectral_data + for (int g = 0; g < numWindowGroups; ++g) { + int sectStart = 0; + for (int i = 0; i < numSec[g]; ++i) { + int codebook = sectCb[g][i]; + if (codebook == ZERO_HCB || codebook > ESC_HCB) { + sectStart = sectEnd[g][i]; + continue; + } + int coefEnd = sectSfbOffset[g][sectEnd[g][i]]; + for (int k = sectSfbOffset[g][sectStart]; k < coefEnd; ) { + if (!CheckOverrun(lenBytes, pos)) { + return false; + } + if (codebook < FIRST_PAIR_HCB) { + int unsigned_, w, x, y, z; + Huffman::DecodeSpectrumQuadBits(codebook - 1, aac, pos, unsigned_, w, x, y, z); + if (unsigned_) { + if (w) ++pos; + if (x) ++pos; + if (y) ++pos; + if (z) ++pos; + } + k += 4; + } + else { + int unsigned_, y, z; + Huffman::DecodeSpectrumPairBits(codebook - 1, aac, pos, unsigned_, y, z); + if (unsigned_) { + if (y) ++pos; + if (z) ++pos; + } + k += 2; + if (codebook == ESC_HCB) { + if (y == ESC_FLAG) { + int count = 0; + while (read_bool(aac, pos)) { + if (++count > 8) { + assert(false); + return false; + } + } + pos += count + 4; + } + if (z == ESC_FLAG) { + int count = 0; + while (read_bool(aac, pos)) { + if (++count > 8) { + assert(false); + return false; + } + } + pos += count + 4; + } + } + } + } + sectStart = sectEnd[g][i]; + } + } + return true; +} + +void DataStreamElement(const uint8_t *aac, size_t &pos) +{ + pos += 4; + bool dataByteAlignFlag = read_bool(aac, pos); + int cnt = read_bits(aac, pos, 8); + if (cnt == 255) { + cnt += read_bits(aac, pos, 8); + } + if (dataByteAlignFlag) { + ByteAlignment(pos); + } + pos += 8 * cnt; +} + +bool ProgramConfigElement(const uint8_t *aac, size_t lenBytes, size_t &pos) +{ + pos += 10; + int numFrontChannelElements = read_bits(aac, pos, 4); + int numSideChannelElements = read_bits(aac, pos, 4); + int numBackChannelElements = read_bits(aac, pos, 4); + int numLfeChannelElements = read_bits(aac, pos, 2); + int numAssocDataElements = read_bits(aac, pos, 3); + int numValidCcElements = read_bits(aac, pos, 4); + bool monoMixdownPresent = read_bool(aac, pos); + if (monoMixdownPresent) { + pos += 4; + } + bool stereoMixdownPresent = read_bool(aac, pos); + if (stereoMixdownPresent) { + pos += 4; + } + bool matrixMixdownIdxPresent = read_bool(aac, pos); + if (matrixMixdownIdxPresent) { + pos += 3; + } + pos += 5 * numFrontChannelElements; + pos += 5 * numSideChannelElements; + pos += 5 * numBackChannelElements; + pos += 4 * numLfeChannelElements; + pos += 4 * numAssocDataElements; + pos += 5 * numValidCcElements; + + if (!CheckOverrun(lenBytes, pos)) { + return false; + } + ByteAlignment(pos); + int commentFieldBytes = read_bits(aac, pos, 8); + pos += 8 * commentFieldBytes; + return true; +} + +bool FillElement(const uint8_t *aac, size_t &pos) +{ + int cnt = read_bits(aac, pos, 4); + if (cnt == 15) { + cnt += read_bits(aac, pos, 8) - 1; + } + if (cnt > 0) { + // extension_payload + int extensionType = read_bits(aac, pos, 4); + if (extensionType == EXT_DYNAMIC_RANGE || + extensionType == EXT_SBR_DATA || + extensionType == EXT_SBR_DATA_CRC) { + return false; + } + pos += 8 * (cnt - 1) + 4; + } + return true; +} + +int RawDataBlock(const uint8_t *aac, size_t lenBytes, size_t &pos, bool is32khz) +{ + if (!CheckOverrun(lenBytes, pos)) { + return -1; + } + int id = read_bits(aac, pos, 3); + if (id == ID_SCE) { + if (SingleChannelElement(aac, lenBytes, pos, is32khz)) { + return id; + } + } + else if (id == ID_DSE) { + DataStreamElement(aac, pos); + return id; + } + else if (id == ID_PCE) { + if (ProgramConfigElement(aac, lenBytes, pos)) { + return id; + } + } + else if (id == ID_FIL) { + if (FillElement(aac, pos)) { + return id; + } + } + else if (id == ID_END) { + return id; + } + return -1; +} + +bool SyncPayload(std::vector &workspace, const uint8_t *payload, size_t lenBytes) +{ + if (!workspace.empty() && workspace[0] == 0) { + // No need to resync + workspace.insert(workspace.end(), payload, payload + lenBytes); + workspace[0] = 0xff; + } + else { + // Resync + workspace.insert(workspace.end(), payload, payload + lenBytes); + size_t i = 0; + for (; i < workspace.size(); ++i) { + if (workspace[i] == 0xff && (i + 1 >= workspace.size() || (workspace[i + 1] & 0xf0) == 0xf0)) { + break; + } + } + workspace.erase(workspace.begin(), workspace.begin() + i); + if (workspace.size() < 2) { + return false; + } + } + assert(workspace[0] == 0xff); + return true; +} + +void SkipPayload(std::vector &workspace, size_t workspaceLenBytes) +{ + workspace.resize(workspaceLenBytes); + size_t i = 0; + while (workspaceLenBytes - i > 0) { + if (workspace[i] != 0xff) { + // Need to resync + workspace.clear(); + return; + } + if (workspaceLenBytes - i < 7) { + break; + } + if ((workspace[i + 1] & 0xf0) != 0xf0) { + workspace.clear(); + return; + } + size_t pos = 30; + size_t frameLenBytes = read_bits(workspace.data() + i, pos, 13); + if (frameLenBytes < 7) { + workspace.clear(); + return; + } + if (workspaceLenBytes - i < frameLenBytes) { + break; + } + i += frameLenBytes; + } + + // Carry over the remaining payload. + workspace.erase(workspace.begin(), workspace.begin() + i); + if (!workspace.empty()) { + assert(workspace[0] == 0xff); + // This 0 means synchronized 0xff. + workspace[0] = 0; + } +} +} + +namespace Aac +{ +bool TransmuxDualMono(std::vector &destLeft, std::vector &destRight, std::vector &workspace, + bool muxLeftToStereo, bool muxRightToStereo, const uint8_t *payload, size_t lenBytes) +{ + destLeft.clear(); + destRight.clear(); + if (!SyncPayload(workspace, payload, lenBytes)) { + // No ADTS frames, done. + return true; + } + size_t workspaceLenBytes = workspace.size(); + workspace.insert(workspace.end(), EXTRA_WORKSPACE_BYTES, 0); + + while (workspaceLenBytes > 0) { + if (workspace[0] != 0xff) { + // Need to resync + workspace.clear(); + return false; + } + if (workspaceLenBytes < 7) { + break; + } + if ((workspace[1] & 0xf0) != 0xf0) { + workspace.clear(); + return false; + } + + // ADTS header + const uint8_t *aac = workspace.data(); + size_t pos = 12; + pos += 3; + bool protectionAbsent = read_bool(aac, pos); + pos += 2; + int samplingFrequencyIndex = read_bits(aac, pos, 4); + // Frequencies other than 48/44.1/32kHz are not supported. + if (samplingFrequencyIndex < 3 || samplingFrequencyIndex > 5) { + SkipPayload(workspace, workspaceLenBytes); + return false; + } + ++pos; + int channelConfiguration = read_bits(aac, pos, 3); + // ARIB STD-B32 seems to define "channel_configuration = 0 and exactly 2 SCEs" as "dual mono". + if (channelConfiguration != 0) { + SkipPayload(workspace, workspaceLenBytes); + return false; + } + pos += 4; + size_t frameLenBytes = read_bits(aac, pos, 13); + if (frameLenBytes < 7) { + workspace.clear(); + return false; + } + if (workspaceLenBytes < frameLenBytes) { + break; + } + pos += 11; + int blocksInFrame = read_bits(aac, pos, 2); + + if (!protectionAbsent) { + // adts(_header)_error_check + pos += (blocksInFrame + 1) * 16; + } + + size_t sceBegin[4][2]; + size_t sceEnd[4][2]; + for (int i = 0; i <= blocksInFrame; ++i) { + int sceCount = 0; + for (;;) { + size_t beginPos = pos; + int id = RawDataBlock(aac, frameLenBytes, pos, samplingFrequencyIndex == 5); + if (id < 0) { + SkipPayload(workspace, workspaceLenBytes); + return false; + } + if (id == ID_END) { + break; + } + if (id == ID_SCE) { + if (sceCount >= 2) { + SkipPayload(workspace, workspaceLenBytes); + return false; + } + sceBegin[i][sceCount] = beginPos; + sceEnd[i][sceCount++] = pos; + } + } + if (sceCount != 2) { + SkipPayload(workspace, workspaceLenBytes); + return false; + } + ByteAlignment(pos); + if (blocksInFrame != 0 && !protectionAbsent) { + // adts_raw_data_block_error_check + pos += 16; + } + } + + assert(pos == frameLenBytes * 8); + if (!CheckOverrun(frameLenBytes, pos)) { + SkipPayload(workspace, workspaceLenBytes); + return false; + } + + // Append 2 ADTS + for (int destIndex = 0; destIndex < 2; ++destIndex) { + std::vector &dest = destIndex == 0 ? destLeft : destRight; + bool muxToStereo = destIndex == 0 ? muxLeftToStereo : muxRightToStereo; + + // ADTS header + size_t destHeadBytes = dest.size(); + dest.insert(dest.end(), aac, aac + 7); + // protection_absent = 1 + dest[destHeadBytes + 1] |= 0x01; + // channel_configuration = 2 or 1 + dest[destHeadBytes + 3] |= muxToStereo ? 0x80 : 0x40; + + for (int i = 0; i <= blocksInFrame; ++i) { + size_t scePos = sceBegin[i][destIndex]; + size_t sceEndPos = sceEnd[i][destIndex]; + if (muxToStereo) { + // CPE + scePos += 3; + // Copy element_instance_tag, common_window = 0 + dest.push_back((ID_CPE << 5) | static_cast(read_bits(aac, scePos, 4) << 1)); + + // Left individual_channel_stream + size_t leftPos = scePos; + while (leftPos + 7 < sceEndPos) { + dest.push_back(static_cast(read_bits(aac, leftPos, 8))); + } + int leftRemain = static_cast(sceEndPos - leftPos); + if (leftRemain != 0) { + dest.push_back(static_cast(read_bits(aac, leftPos, leftRemain)) << (8 - leftRemain)); + } + // Right individual_channel_stream + if (leftRemain != 0) { + dest.back() |= static_cast(read_bits(aac, scePos, 8 - leftRemain)); + } + } + // SCE or Right individual_channel_stream + while (scePos + 7 < sceEndPos) { + dest.push_back(static_cast(read_bits(aac, scePos, 8))); + } + int sceRemain = static_cast(sceEndPos - scePos); + if (sceRemain != 0) { + dest.push_back((static_cast(read_bits(aac, scePos, sceRemain)) << (8 - sceRemain)) | (0xe0 >> sceRemain)); + } + if (sceRemain == 0 || sceRemain >= 6) { + // ID_END, remaining bits are filled with 0. + dest.push_back((0x60e0 >> sceRemain) & 0xe0); + } + } + + // aac_frame_length + size_t destFrameLenBytes = dest.size() - destHeadBytes; + dest[destHeadBytes + 3] = (dest[destHeadBytes + 3] & 0xfc) | static_cast(destFrameLenBytes >> 11); + dest[destHeadBytes + 4] = static_cast(destFrameLenBytes >> 3); + dest[destHeadBytes + 5] = static_cast(destFrameLenBytes << 5) | (dest[destHeadBytes + 5] & 0x1f); + } + + // Erase current frame. + workspace.erase(workspace.begin(), workspace.begin() + frameLenBytes); + workspaceLenBytes -= frameLenBytes; + } + + SkipPayload(workspace, workspaceLenBytes); + return true; +} + +bool TransmuxMonoToStereo(std::vector &dest, std::vector &workspace, const uint8_t *payload, size_t lenBytes) +{ + dest.clear(); + if (!SyncPayload(workspace, payload, lenBytes)) { + // No ADTS frames, done. + return true; + } + size_t workspaceLenBytes = workspace.size(); + workspace.insert(workspace.end(), EXTRA_WORKSPACE_BYTES, 0); + + while (workspaceLenBytes > 0) { + if (workspace[0] != 0xff) { + // Need to resync + workspace.clear(); + return false; + } + if (workspaceLenBytes < 7) { + break; + } + if ((workspace[1] & 0xf0) != 0xf0) { + workspace.clear(); + return false; + } + + // ADTS header + const uint8_t *aac = workspace.data(); + size_t pos = 12; + pos += 3; + bool protectionAbsent = read_bool(aac, pos); + pos += 2; + int samplingFrequencyIndex = read_bits(aac, pos, 4); + // Frequencies other than 48/44.1/32kHz are not supported. + if (samplingFrequencyIndex < 3 || samplingFrequencyIndex > 5) { + SkipPayload(workspace, workspaceLenBytes); + return false; + } + ++pos; + int channelConfiguration = read_bits(aac, pos, 3); + if (channelConfiguration != 1) { + SkipPayload(workspace, workspaceLenBytes); + return false; + } + pos += 4; + size_t frameLenBytes = read_bits(aac, pos, 13); + if (frameLenBytes < 7) { + workspace.clear(); + return false; + } + if (workspaceLenBytes < frameLenBytes) { + break; + } + pos += 11; + int blocksInFrame = read_bits(aac, pos, 2); + + if (!protectionAbsent) { + // adts(_header)_error_check + pos += (blocksInFrame + 1) * 16; + } + + size_t sceBegin[4]; + size_t sceEnd[4]; + for (int i = 0; i <= blocksInFrame; ++i) { + bool sceFound = false; + for (;;) { + size_t beginPos = pos; + int id = RawDataBlock(aac, frameLenBytes, pos, samplingFrequencyIndex == 5); + if (id < 0) { + SkipPayload(workspace, workspaceLenBytes); + return false; + } + if (id == ID_END) { + break; + } + if (id == ID_SCE) { + if (sceFound) { + SkipPayload(workspace, workspaceLenBytes); + return false; + } + sceBegin[i] = beginPos; + sceEnd[i] = pos; + sceFound = true; + } + } + if (!sceFound) { + SkipPayload(workspace, workspaceLenBytes); + return false; + } + ByteAlignment(pos); + if (blocksInFrame != 0 && !protectionAbsent) { + // adts_raw_data_block_error_check + pos += 16; + } + } + + assert(pos == frameLenBytes * 8); + if (!CheckOverrun(frameLenBytes, pos)) { + SkipPayload(workspace, workspaceLenBytes); + return false; + } + + // ADTS header + size_t destHeadBytes = dest.size(); + dest.insert(dest.end(), aac, aac + 7); + // protection_absent = 1 + dest[destHeadBytes + 1] |= 0x01; + // channel_configuration = 2 + dest[destHeadBytes + 3] = (dest[destHeadBytes + 3] & 0x3f) | 0x80; + + for (int i = 0; i <= blocksInFrame; ++i) { + size_t scePos = sceBegin[i]; + size_t sceEndPos = sceEnd[i]; + { + // CPE + scePos += 3; + // Copy element_instance_tag, common_window = 0 + dest.push_back((ID_CPE << 5) | static_cast(read_bits(aac, scePos, 4) << 1)); + + // Left individual_channel_stream + size_t leftPos = scePos; + while (leftPos + 7 < sceEndPos) { + dest.push_back(static_cast(read_bits(aac, leftPos, 8))); + } + int leftRemain = static_cast(sceEndPos - leftPos); + if (leftRemain != 0) { + dest.push_back(static_cast(read_bits(aac, leftPos, leftRemain)) << (8 - leftRemain)); + } + // Right individual_channel_stream + if (leftRemain != 0) { + dest.back() |= static_cast(read_bits(aac, scePos, 8 - leftRemain)); + } + } + while (scePos + 7 < sceEndPos) { + dest.push_back(static_cast(read_bits(aac, scePos, 8))); + } + int sceRemain = static_cast(sceEndPos - scePos); + if (sceRemain != 0) { + dest.push_back((static_cast(read_bits(aac, scePos, sceRemain)) << (8 - sceRemain)) | (0xe0 >> sceRemain)); + } + if (sceRemain == 0 || sceRemain >= 6) { + // ID_END, remaining bits are filled with 0. + dest.push_back((0x60e0 >> sceRemain) & 0xe0); + } + } + + // aac_frame_length + size_t destFrameLenBytes = dest.size() - destHeadBytes; + dest[destHeadBytes + 3] = (dest[destHeadBytes + 3] & 0xfc) | static_cast(destFrameLenBytes >> 11); + dest[destHeadBytes + 4] = static_cast(destFrameLenBytes >> 3); + dest[destHeadBytes + 5] = static_cast(destFrameLenBytes << 5) | (dest[destHeadBytes + 5] & 0x1f); + + // Erase current frame. + workspace.erase(workspace.begin(), workspace.begin() + frameLenBytes); + workspaceLenBytes -= frameLenBytes; + } + + SkipPayload(workspace, workspaceLenBytes); + return true; +} +} diff --git a/tsreadex/aac.hpp b/tsreadex/aac.hpp new file mode 100644 index 0000000..eb82bbd --- /dev/null +++ b/tsreadex/aac.hpp @@ -0,0 +1,15 @@ +#ifndef INCLUDE_AAC_HPP +#define INCLUDE_AAC_HPP + +#include +#include +#include + +namespace Aac +{ +bool TransmuxDualMono(std::vector &destLeft, std::vector &destRight, std::vector &workspace, + bool muxLeftToStereo, bool muxRightToStereo, const uint8_t *payload, size_t lenBytes); +bool TransmuxMonoToStereo(std::vector &dest, std::vector &workspace, const uint8_t *payload, size_t lenBytes); +} + +#endif diff --git a/tsreadex/huffman.cpp b/tsreadex/huffman.cpp new file mode 100644 index 0000000..dbdb9b2 --- /dev/null +++ b/tsreadex/huffman.cpp @@ -0,0 +1,383 @@ +#include "huffman.hpp" +#include "util.hpp" + +namespace +{ +const uint16_t SCALEFACTOR_TREE[][2] = +{ + {560, 1}, { 92, 2}, { 90, 3}, { 87, 4}, { 83, 5}, + { 79, 6}, { 74, 7}, { 72, 8}, { 67, 9}, { 62, 10}, + { 56, 11}, { 52, 12}, { 44, 13}, { 14, 22}, { 18, 15}, + { 16, 98}, { 17, 106}, {500, 105}, { 42, 19}, { 21, 20}, + {501, 502}, {519, 503}, { 29, 23}, { 24, 35}, { 27, 25}, + { 26, 33}, {619, 504}, { 28, 109}, {510, 505}, {110, 30}, + {113, 31}, { 32, 34}, {618, 506}, {507, 515}, {508, 509}, + { 39, 36}, { 37, 38}, {511, 512}, {514, 513}, { 40, 41}, + {516, 518}, {520, 517}, {523, 43}, {590, 521}, { 49, 45}, + { 48, 46}, {588, 47}, {525, 522}, {528, 524}, { 51, 50}, + {526, 527}, {586, 529}, { 55, 53}, { 97, 54}, {530, 531}, + {584, 532}, { 60, 57}, { 58, 59}, {585, 533}, {536, 534}, + {583, 61}, {537, 535}, { 66, 63}, { 65, 64}, {582, 538}, + {581, 539}, {580, 540}, { 70, 68}, { 71, 69}, {542, 541}, + {545, 543}, {544, 579}, { 73, 94}, {574, 546}, { 78, 75}, + { 77, 76}, {573, 547}, {572, 548}, {549, 571}, { 82, 80}, + {551, 81}, {570, 550}, {552, 569}, { 86, 84}, {567, 85}, + {553, 568}, {566, 554}, { 89, 88}, {555, 565}, {556, 564}, + {562, 91}, {557, 563}, {559, 93}, {561, 558}, { 95, 96}, + {576, 575}, {577, 578}, {587, 589}, { 99, 102}, {100, 101}, + {597, 591}, {592, 593}, {103, 104}, {594, 595}, {596, 604}, + {598, 599}, {107, 108}, {600, 601}, {602, 617}, {603, 620}, + {116, 111}, {119, 112}, {610, 605}, {114, 115}, {606, 607}, + {608, 609}, {117, 118}, {611, 612}, {613, 614}, {615, 616}, +}; + +const uint16_t SPECTRUM1_TREE[][2] = +{ + {540, 1}, { 38, 2}, { 19, 3}, { 33, 4}, { 28, 5}, + { 11, 6}, { 16, 7}, { 25, 8}, { 9, 14}, { 10, 58}, + {500, 574}, { 12, 55}, { 13, 59}, {521, 501}, { 15, 76}, + {518, 502}, { 23, 17}, { 18, 46}, {511, 503}, { 52, 20}, + { 21, 44}, { 22, 73}, {504, 528}, { 24, 78}, {505, 509}, + { 50, 26}, { 32, 27}, {572, 506}, { 47, 29}, { 42, 30}, + { 31, 77}, {507, 529}, {580, 508}, { 34, 36}, { 74, 35}, + {510, 568}, { 37, 41}, {512, 566}, { 39, 60}, { 40, 71}, + {567, 513}, {514, 530}, { 72, 43}, {525, 515}, { 45, 62}, + {516, 544}, {517, 571}, { 48, 63}, { 49, 75}, {573, 519}, + { 51, 57}, {560, 520}, { 53, 65}, { 54, 70}, {558, 522}, + { 79, 56}, {523, 579}, {524, 556}, {562, 526}, {527, 553}, + { 69, 61}, {543, 531}, {570, 532}, { 67, 64}, {533, 555}, + { 66, 68}, {534, 542}, {547, 535}, {576, 536}, {541, 537}, + {538, 546}, {539, 549}, {565, 545}, {564, 548}, {552, 550}, + {561, 551}, {554, 578}, {559, 557}, {575, 563}, {569, 577}, +}; + +const uint16_t SPECTRUM2_TREE[][2] = +{ + { 38, 1}, { 20, 2}, { 9, 3}, { 16, 4}, { 29, 5}, + { 6, 13}, { 7, 24}, {520, 8}, {556, 500}, { 46, 10}, + { 57, 11}, { 61, 12}, {515, 501}, { 27, 14}, { 15, 79}, + {554, 502}, { 35, 17}, { 18, 59}, { 26, 19}, {505, 503}, + { 21, 32}, { 22, 64}, { 37, 23}, {564, 504}, { 56, 25}, + {580, 506}, {507, 517}, { 45, 28}, {508, 572}, { 30, 66}, + { 55, 31}, {509, 551}, { 42, 33}, { 34, 76}, {530, 510}, + { 36, 49}, {511, 555}, {512, 558}, { 39, 51}, {540, 40}, + {567, 41}, {513, 541}, { 44, 43}, {528, 514}, {516, 550}, + {562, 518}, { 73, 47}, { 75, 48}, {519, 533}, {573, 50}, + {559, 521}, { 62, 52}, { 53, 70}, {549, 54}, {534, 522}, + {553, 523}, {524, 526}, { 78, 58}, {571, 525}, { 60, 74}, + {527, 569}, {529, 579}, { 69, 63}, {531, 543}, { 68, 65}, + {568, 532}, { 67, 77}, {557, 535}, {536, 570}, {537, 539}, + { 72, 71}, {548, 538}, {546, 542}, {544, 566}, {563, 545}, + {547, 565}, {576, 552}, {577, 560}, {561, 575}, {574, 578}, +}; + +const uint16_t SPECTRUM3_TREE[][2] = +{ + {500, 1}, { 2, 4}, { 3, 10}, {527, 501}, { 11, 5}, + { 6, 13}, { 52, 7}, { 55, 8}, {537, 9}, {554, 502}, + {509, 503}, { 12, 27}, {536, 504}, { 14, 22}, { 15, 17}, + { 16, 20}, {505, 563}, { 18, 29}, { 32, 19}, {566, 506}, + {548, 21}, {507, 516}, { 34, 23}, { 24, 38}, { 25, 53}, + { 56, 26}, {508, 517}, { 28, 31}, {512, 510}, { 33, 30}, + {518, 511}, {530, 513}, {545, 514}, {521, 515}, { 44, 35}, + { 57, 36}, { 37, 63}, {534, 519}, { 48, 39}, { 46, 40}, + { 50, 41}, { 42, 65}, {553, 43}, {520, 560}, { 67, 45}, + {522, 542}, { 47, 59}, {524, 523}, { 49, 61}, {525, 70}, + { 51, 68}, {526, 579}, {528, 539}, { 69, 54}, {529, 555}, + {540, 531}, {564, 532}, {543, 58}, {546, 533}, { 60, 78}, + {535, 573}, { 62, 64}, {538, 558}, {567, 541}, {544, 576}, + { 66, 71}, {547, 75}, {557, 549}, {570, 550}, {575, 551}, + {572, 552}, { 77, 72}, { 74, 73}, {556, 76}, {571, 559}, + {561, 568}, {574, 562}, {565, 79}, {569, 578}, {580, 577}, +}; + +const uint16_t SPECTRUM4_TREE[][2] = +{ + { 1, 4}, { 32, 2}, { 54, 3}, {536, 500}, { 5, 8}, + { 16, 6}, { 31, 7}, {501, 510}, { 14, 9}, { 17, 10}, + { 11, 24}, { 22, 12}, { 55, 13}, {502, 21}, { 15, 62}, + {503, 509}, {504, 530}, { 34, 18}, { 56, 19}, { 30, 20}, + {521, 505}, {518, 506}, { 37, 23}, {529, 507}, { 48, 25}, + { 38, 26}, { 45, 27}, { 28, 52}, { 29, 41}, {508, 560}, + {555, 511}, {528, 512}, { 33, 60}, {540, 513}, { 35, 42}, + {516, 36}, {514, 542}, {515, 519}, { 39, 58}, { 40, 44}, + {517, 573}, {520, 556}, { 43, 61}, {522, 532}, {523, 561}, + { 75, 46}, { 79, 47}, {524, 572}, { 66, 49}, { 72, 50}, + { 73, 51}, {569, 525}, { 53, 76}, {580, 526}, {531, 527}, + {533, 554}, { 57, 68}, {534, 563}, { 59, 69}, {535, 579}, + {537, 539}, {546, 538}, { 63, 70}, { 65, 64}, {549, 541}, + {567, 543}, { 74, 67}, {570, 544}, {557, 545}, {547, 559}, + { 78, 71}, {548, 558}, {550, 568}, {551, 575}, {552, 576}, + {565, 553}, {578, 77}, {574, 562}, {566, 564}, {571, 577}, +}; + +const uint16_t SPECTRUM5_TREE[][2] = +{ + {540, 1}, { 68, 2}, { 66, 3}, { 53, 4}, { 38, 5}, + { 34, 6}, { 19, 7}, { 16, 8}, { 13, 9}, { 28, 10}, + { 31, 11}, { 30, 12}, {580, 500}, { 27, 14}, { 50, 15}, + {563, 501}, { 32, 17}, { 18, 77}, {502, 578}, { 23, 20}, + { 21, 25}, { 22, 63}, {503, 544}, { 24, 79}, {536, 504}, + { 73, 26}, {505, 545}, {570, 506}, { 29, 49}, {507, 571}, + {572, 508}, {573, 509}, { 33, 48}, {564, 510}, { 51, 35}, + { 36, 46}, { 37, 62}, {511, 565}, { 42, 39}, { 40, 44}, + { 41, 64}, {512, 552}, { 43, 74}, {513, 567}, { 45, 72}, + {514, 566}, { 78, 47}, {515, 519}, {516, 526}, {517, 579}, + {518, 574}, { 61, 52}, {520, 556}, { 57, 54}, { 55, 59}, + { 56, 65}, {521, 559}, { 58, 75}, {522, 542}, { 60, 71}, + {523, 557}, {524, 560}, {525, 555}, {575, 527}, {568, 528}, + {529, 551}, { 70, 67}, {530, 550}, { 69, 76}, {531, 549}, + {548, 532}, {533, 547}, {546, 534}, {553, 535}, {537, 543}, + {558, 538}, {541, 539}, {554, 562}, {569, 561}, {577, 576}, +}; + +const uint16_t SPECTRUM6_TREE[][2] = +{ + { 60, 1}, { 47, 2}, { 32, 3}, { 28, 4}, { 15, 5}, + { 12, 6}, { 22, 7}, { 77, 8}, { 11, 9}, { 21, 10}, + {500, 572}, {507, 501}, { 13, 19}, { 14, 42}, {506, 502}, + { 25, 16}, { 17, 65}, { 56, 18}, {505, 503}, { 20, 55}, + {504, 578}, {580, 508}, { 74, 23}, {544, 24}, {509, 517}, + { 40, 26}, { 27, 41}, {564, 510}, { 36, 29}, { 30, 43}, + {569, 31}, {511, 525}, { 45, 33}, { 38, 34}, { 35, 57}, + {512, 552}, { 37, 67}, {567, 513}, { 39, 64}, {514, 568}, + {515, 570}, {516, 545}, {562, 518}, { 76, 44}, {555, 519}, + { 54, 46}, {520, 560}, { 48, 51}, {530, 49}, { 75, 50}, + {523, 521}, { 52, 58}, { 53, 72}, {522, 533}, {556, 524}, + {574, 526}, {527, 577}, {546, 528}, { 68, 59}, {529, 542}, + { 69, 61}, { 62, 63}, {531, 550}, {532, 548}, {566, 534}, + { 73, 66}, {535, 536}, {537, 543}, {551, 538}, { 71, 70}, + {539, 541}, {540, 549}, {558, 547}, {553, 575}, {576, 554}, + {557, 559}, {561, 565}, { 78, 79}, {563, 573}, {571, 579}, +}; + +const uint16_t SPECTRUM7_TREE[][2] = +{ + {500, 1}, { 2, 3}, {508, 501}, { 4, 7}, {509, 5}, + { 28, 6}, {516, 502}, { 8, 11}, { 29, 9}, { 10, 31}, + {503, 38}, { 12, 18}, { 13, 15}, { 39, 14}, {504, 532}, + { 16, 34}, { 40, 17}, {542, 505}, { 19, 23}, { 41, 20}, + { 36, 21}, { 22, 47}, {530, 506}, { 43, 24}, { 25, 51}, + { 50, 26}, {531, 27}, {556, 507}, {517, 510}, { 30, 37}, + {525, 511}, { 32, 33}, {512, 533}, {513, 541}, { 54, 35}, + {514, 535}, {550, 515}, {518, 524}, {519, 526}, {527, 520}, + {534, 521}, { 46, 42}, {543, 522}, { 48, 44}, { 45, 59}, + {551, 523}, {529, 528}, {548, 536}, { 49, 55}, {557, 537}, + {545, 538}, { 52, 57}, { 56, 53}, {560, 539}, {549, 540}, + {558, 544}, {553, 546}, { 58, 60}, {547, 561}, {559, 552}, + { 61, 62}, {562, 554}, {555, 563}, +}; + +const uint16_t SPECTRUM8_TREE[][2] = +{ + { 1, 6}, { 29, 2}, { 5, 3}, {518, 4}, {500, 516}, + {510, 501}, { 7, 10}, { 8, 32}, { 9, 31}, {502, 525}, + { 11, 17}, { 12, 14}, { 13, 50}, {503, 535}, { 35, 15}, + { 51, 16}, {544, 504}, { 37, 18}, { 19, 22}, { 20, 47}, + { 21, 56}, {552, 505}, { 40, 23}, { 24, 26}, { 57, 25}, + {506, 561}, { 62, 27}, { 59, 28}, {507, 563}, {509, 30}, + {517, 508}, {511, 526}, { 42, 33}, { 34, 43}, {512, 534}, + { 44, 36}, {513, 543}, { 45, 38}, { 39, 52}, {549, 514}, + { 41, 54}, {515, 546}, {519, 49}, {520, 524}, {541, 521}, + { 55, 46}, {522, 550}, { 48, 60}, {558, 523}, {527, 533}, + {528, 542}, {529, 536}, { 53, 58}, {530, 551}, {531, 61}, + {537, 532}, {538, 557}, {548, 539}, {545, 540}, {547, 556}, + {553, 559}, {554, 560}, {562, 555}, +}; + +const uint16_t SPECTRUM9_TREE[][2] = +{ + {500, 1}, { 2, 3}, {513, 501}, { 4, 7}, {514, 5}, + { 53, 6}, {526, 502}, { 8, 16}, { 9, 12}, { 74, 10}, + {516, 11}, {539, 503}, { 54, 13}, { 56, 14}, { 90, 15}, + {504, 552}, { 17, 26}, { 18, 22}, { 57, 19}, {112, 20}, + {555, 21}, {505, 532}, { 59, 23}, { 24, 77}, { 25, 79}, + {592, 506}, { 27, 34}, { 28, 65}, { 63, 29}, { 30, 32}, + { 31, 91}, {507, 619}, { 33, 124}, {546, 508}, { 35, 44}, + { 36, 40}, { 83, 37}, { 38, 69}, { 39, 125}, {509, 645}, + { 71, 41}, { 94, 42}, { 43, 138}, {647, 510}, { 86, 45}, + { 46, 129}, { 47, 50}, {110, 48}, { 49, 128}, {511, 662}, + {121, 51}, { 52, 145}, {512, 650}, {527, 515}, { 75, 55}, + {517, 553}, {530, 518}, { 76, 58}, {519, 543}, { 60, 102}, + { 61, 62}, {565, 520}, {544, 521}, { 64, 114}, {522, 593}, + { 80, 66}, { 67, 92}, { 68, 116}, {523, 595}, {106, 70}, + {610, 524}, {117, 72}, { 73, 163}, {537, 525}, {540, 528}, + {529, 541}, {566, 531}, { 78, 104}, {545, 533}, {606, 534}, + { 81, 136}, { 82, 135}, {535, 570}, {119, 84}, {609, 85}, + {633, 536}, { 87, 96}, {107, 88}, { 89, 109}, {538, 623}, + {554, 542}, {547, 558}, {105, 93}, {548, 644}, { 95, 126}, + {549, 574}, { 97, 99}, {127, 98}, {550, 575}, {100, 139}, + {141, 101}, {661, 551}, {103, 113}, {605, 556}, {557, 618}, + {560, 559}, {657, 561}, {155, 108}, {562, 586}, {624, 563}, + {111, 120}, {636, 564}, {567, 579}, {568, 580}, {115, 123}, + {578, 569}, {583, 571}, {152, 118}, {572, 596}, {573, 617}, + {600, 576}, {122, 142}, {637, 577}, {581, 607}, {631, 582}, + {621, 584}, {585, 611}, {643, 587}, {588, 656}, {130, 146}, + {131, 133}, {143, 132}, {663, 589}, {150, 134}, {603, 590}, + {604, 591}, {137, 144}, {594, 632}, {597, 659}, {140, 164}, + {648, 598}, {612, 599}, {601, 625}, {638, 602}, {620, 608}, + {613, 626}, {147, 156}, {148, 153}, {149, 151}, {614, 639}, + {615, 651}, {616, 627}, {622, 634}, {154, 159}, {628, 629}, + {630, 635}, {157, 160}, {158, 165}, {640, 652}, {641, 665}, + {167, 161}, {162, 166}, {642, 654}, {658, 646}, {660, 649}, + {664, 653}, {655, 668}, {666, 667}, +}; + +const uint16_t SPECTRUM10_TREE[][2] = +{ + { 6, 1}, { 2, 19}, { 3, 15}, { 4, 13}, { 59, 5}, + {500, 555}, { 55, 7}, { 8, 10}, { 9, 103}, {501, 516}, + { 86, 11}, { 12, 87}, {526, 502}, {102, 14}, {503, 556}, + { 60, 16}, { 89, 17}, {111, 18}, {579, 504}, { 20, 25}, + { 63, 21}, { 22, 68}, { 23, 66}, {121, 24}, {592, 505}, + { 26, 31}, { 27, 71}, {124, 28}, {135, 29}, {105, 30}, + {535, 506}, { 32, 39}, { 75, 33}, { 34, 79}, { 35, 37}, + { 36, 107}, {507, 612}, {129, 38}, {648, 508}, { 40, 44}, + {108, 41}, {143, 42}, { 43, 120}, {657, 509}, { 82, 45}, + { 46, 51}, { 47, 49}, { 48, 162}, {510, 603}, {166, 50}, + {511, 654}, {154, 52}, {159, 53}, { 54, 167}, {667, 512}, + { 58, 56}, {527, 57}, {528, 513}, {514, 515}, {517, 553}, + { 88, 61}, { 62, 104}, {518, 566}, { 64, 91}, { 65, 122}, + {519, 545}, { 67, 147}, {605, 520}, { 94, 69}, { 70, 114}, + {521, 547}, { 72, 96}, {115, 73}, { 74, 152}, {522, 598}, + {117, 76}, {149, 77}, { 78, 156}, {636, 523}, { 99, 80}, + {119, 81}, {524, 658}, { 83, 132}, {101, 84}, {614, 85}, + {627, 525}, {529, 542}, {530, 554}, {531, 567}, {112, 90}, + {580, 532}, {113, 92}, { 93, 123}, {546, 533}, { 95, 138}, + {559, 534}, {127, 97}, { 98, 106}, {536, 631}, {100, 142}, + {647, 537}, {662, 538}, {543, 539}, {541, 540}, {568, 544}, + {548, 618}, {549, 623}, {635, 550}, {130, 109}, {164, 110}, + {661, 551}, {581, 552}, {569, 557}, {558, 137}, {608, 560}, + {151, 116}, {561, 632}, {141, 118}, {562, 591}, {637, 563}, + {564, 626}, {594, 565}, {570, 582}, {571, 606}, {125, 126}, + {572, 609}, {573, 139}, {148, 128}, {574, 634}, {575, 613}, + {157, 131}, {649, 576}, {133, 145}, {165, 134}, {602, 577}, + {136, 140}, {619, 578}, {583, 593}, {584, 596}, {597, 585}, + {586, 620}, {587, 604}, {601, 588}, {144, 161}, {589, 617}, + {146, 153}, {590, 639}, {607, 595}, {599, 633}, {645, 150}, + {600, 646}, {610, 621}, {611, 622}, {615, 664}, {158, 155}, + {656, 616}, {644, 624}, {625, 659}, {628, 641}, {163, 160}, + {629, 655}, {638, 630}, {643, 640}, {665, 642}, {660, 650}, + {651, 663}, {652, 653}, {666, 668}, +}; + +const uint16_t SPECTRUM11_TREE[][2] = +{ + { 1, 13}, { 2, 6}, { 3, 4}, {500, 518}, { 71, 5}, + {501, 535}, { 72, 7}, { 8, 10}, { 9, 76}, {537, 502}, + {116, 11}, { 12, 187}, {551, 503}, { 14, 20}, { 15, 84}, + { 78, 16}, { 81, 17}, {169, 18}, { 19, 188}, {504, 550}, + { 21, 31}, { 22, 26}, { 23, 89}, {175, 24}, {202, 25}, + {505, 228}, { 27, 94}, { 28, 132}, {191, 29}, { 93, 30}, + {673, 506}, { 32, 38}, { 33, 102}, { 67, 34}, { 35, 98}, + { 36, 141}, { 37, 264}, {507, 564}, { 39, 48}, { 40, 44}, + { 41, 107}, { 42, 218}, {101, 43}, {508, 728}, {164, 45}, + {236, 46}, {221, 47}, {653, 509}, {110, 49}, { 50, 54}, + { 51, 62}, {253, 52}, {252, 53}, {510, 716}, { 55, 58}, + { 56, 279}, {269, 57}, {721, 511}, {271, 59}, { 60, 65}, + {753, 61}, {514, 512}, {260, 63}, { 64, 285}, {513, 750}, + {284, 66}, {515, 770}, {135, 68}, {245, 69}, {217, 70}, + {516, 581}, {788, 517}, { 73, 74}, {519, 536}, { 75, 115}, + {520, 552}, {554, 77}, {569, 521}, { 79, 119}, {118, 80}, + {522, 588}, { 82, 167}, {150, 83}, {523, 584}, { 85, 125}, + {122, 86}, {212, 87}, {223, 88}, {533, 524}, { 90, 129}, + { 91, 155}, { 92, 226}, {720, 525}, {627, 526}, { 95, 158}, + {230, 96}, { 97, 214}, {690, 527}, {204, 99}, {274, 100}, + {528, 580}, {529, 729}, {103, 137}, {104, 183}, {105, 162}, + {161, 106}, {530, 706}, {146, 108}, {109, 186}, {759, 531}, + {209, 111}, {112, 238}, {222, 113}, {267, 114}, {532, 682}, + {553, 534}, {117, 148}, {570, 538}, {539, 572}, {149, 120}, + {171, 121}, {540, 603}, {123, 151}, {286, 124}, {780, 541}, + {172, 126}, {127, 153}, {128, 262}, {542, 592}, {130, 177}, + {131, 229}, {626, 543}, {179, 133}, {134, 157}, {544, 659}, + {136, 194}, {724, 545}, {138, 143}, {139, 206}, {140, 247}, + {695, 546}, {273, 142}, {547, 726}, {196, 144}, {266, 145}, + {548, 614}, {208, 147}, {549, 760}, {571, 555}, {556, 589}, + {605, 557}, {152, 189}, {621, 558}, {227, 154}, {559, 585}, + {190, 156}, {560, 641}, {561, 611}, {159, 181}, {160, 193}, + {628, 562}, {688, 563}, {163, 195}, {630, 565}, {233, 165}, + {166, 256}, {600, 566}, {168, 287}, {567, 777}, {199, 170}, + {568, 574}, {573, 604}, {173, 200}, {174, 216}, {575, 783}, + {270, 176}, {624, 576}, {243, 178}, {577, 610}, {242, 180}, + {658, 578}, {182, 203}, {579, 689}, {184, 224}, {185, 265}, + {742, 582}, {664, 583}, {586, 587}, {590, 601}, {607, 591}, + {593, 737}, {198, 192}, {594, 643}, {595, 671}, {772, 596}, + {597, 598}, {248, 197}, {679, 599}, {602, 657}, {606, 778}, + {241, 201}, {608, 669}, {609, 625}, {723, 612}, {205, 255}, + {646, 613}, {207, 232}, {615, 680}, {705, 616}, {249, 210}, + {257, 211}, {714, 617}, {213, 215}, {618, 782}, {619, 676}, + {622, 620}, {623, 784}, {629, 645}, {219, 220}, {631, 711}, + {632, 758}, {756, 633}, {634, 251}, {781, 635}, {275, 225}, + {710, 636}, {637, 754}, {639, 638}, {640, 787}, {642, 655}, + {231, 244}, {674, 644}, {757, 647}, {235, 234}, {730, 648}, + {745, 649}, {276, 237}, {762, 650}, {258, 239}, {240, 281}, + {699, 651}, {652, 773}, {672, 654}, {656, 771}, {675, 660}, + {692, 246}, {691, 661}, {662, 743}, {663, 744}, {250, 277}, + {666, 665}, {667, 763}, {715, 668}, {278, 254}, {748, 670}, + {678, 677}, {681, 697}, {683, 747}, {259, 268}, {684, 732}, + {263, 261}, {685, 734}, {686, 785}, {687, 718}, {693, 722}, + {694, 741}, {696, 739}, {698, 701}, {731, 700}, {702, 751}, + {786, 703}, {283, 272}, {736, 704}, {725, 707}, {740, 708}, + {709, 727}, {712, 761}, {713, 746}, {717, 764}, {282, 280}, + {768, 719}, {749, 733}, {735, 767}, {738, 752}, {769, 755}, + {765, 766}, {779, 774}, {775, 776}, +}; + +const uint16_t (*const SPECTRUM_TREES[])[2] = +{ + SPECTRUM1_TREE, + SPECTRUM2_TREE, + SPECTRUM3_TREE, + SPECTRUM4_TREE, + SPECTRUM5_TREE, + SPECTRUM6_TREE, + SPECTRUM7_TREE, + SPECTRUM8_TREE, + SPECTRUM9_TREE, + SPECTRUM10_TREE, + SPECTRUM11_TREE, +}; + +inline uint16_t DecodeBits(const uint16_t (*const tree)[2], const uint8_t *data, size_t &pos) +{ + // <500: node, else: leaf + uint16_t index = 0; + do { + index = tree[index][extract_bit(data, pos++)]; + } + while (index < 500); + return index - 500; +} +} + +namespace Huffman +{ +int DecodeScalefactorBits(const uint8_t *data, size_t &pos) +{ + // ISO/IEC 13818-7 Scalefactor Huffman codebook parameters + return DecodeBits(SCALEFACTOR_TREE, data, pos) - 60; +} + +void DecodeSpectrumQuadBits(int codebook, const uint8_t *data, size_t &pos, int &unsigned_, int &w, int &x, int &y, int &z) +{ + // ISO/IEC 13818-7 Spectrum Huffman codebooks parameters + uint16_t index = DecodeBits(SPECTRUM_TREES[codebook], data, pos); + int un = codebook >= 2; + unsigned_ = un; + w = index / 27 + un - 1; + x = index / 9 % 3 + un - 1; + y = index / 3 % 3 + un - 1; + z = index % 3 + un - 1; +} + +void DecodeSpectrumPairBits(int codebook, const uint8_t *data, size_t &pos, int &unsigned_, int &y, int &z) +{ + // ISO/IEC 13818-7 Spectrum Huffman codebooks parameters + uint16_t index = DecodeBits(SPECTRUM_TREES[codebook], data, pos); + int mod = codebook >= 10 ? 17 : codebook >= 8 ? 13 : codebook >= 6 ? 8 : 9; + int un = codebook >= 6; + unsigned_ = un; + y = index / mod + un * 4 - 4; + z = index % mod + un * 4 - 4; +} +} diff --git a/tsreadex/huffman.hpp b/tsreadex/huffman.hpp new file mode 100644 index 0000000..d15acf7 --- /dev/null +++ b/tsreadex/huffman.hpp @@ -0,0 +1,15 @@ +#ifndef INCLUDE_HUFFMAN_HPP +#define INCLUDE_HUFFMAN_HPP + +#include +#include + +namespace Huffman +{ +const size_t MAX_CODEWORD_LEN = 19; +int DecodeScalefactorBits(const uint8_t *data, size_t &pos); +void DecodeSpectrumQuadBits(int codebook, const uint8_t *data, size_t &pos, int &unsigned_, int &w, int &x, int &y, int &z); +void DecodeSpectrumPairBits(int codebook, const uint8_t *data, size_t &pos, int &unsigned_, int &y, int &z); +} + +#endif diff --git a/tsreadex/id3conv.cpp b/tsreadex/id3conv.cpp new file mode 100644 index 0000000..170abcf --- /dev/null +++ b/tsreadex/id3conv.cpp @@ -0,0 +1,370 @@ +#include "id3conv.hpp" +#include + +CID3Converter::CID3Converter() + : m_enabled(false) + , m_treatUnknownPrivateDataAsSuperimpose(false) + , m_insertInappropriate5BytesIntoPesPayload(false) + , m_forceMonotonousPts(false) + , m_lastID3Pts(-1) + , m_firstPmtPid(0) + , m_captionPid(0) + , m_superimposePid(0) + , m_pcrPid(0) + , m_pcr(-1) + , m_id3Pid(0) + , m_id3Counter(0) + , m_pmtCounter(0) +{ + static const PAT zeroPat = {}; + m_pat = zeroPat; + m_firstPmtPsi = zeroPat.psi; +} + +void CID3Converter::SetOption(int flags) +{ + m_enabled = !!(flags & 1); + m_treatUnknownPrivateDataAsSuperimpose = !!(flags & 2); + m_insertInappropriate5BytesIntoPesPayload = !!(flags & 4); + m_forceMonotonousPts = !!(flags & 8); +} + +void CID3Converter::AddPacket(const uint8_t *packet) +{ + if (!m_enabled) { + m_packets.insert(m_packets.end(), packet, packet + 188); + return; + } + + int unitStart = extract_ts_header_unit_start(packet); + int pid = extract_ts_header_pid(packet); + int adaptation = extract_ts_header_adaptation(packet); + int counter = extract_ts_header_counter(packet); + int payloadSize = get_ts_payload_size(packet); + const uint8_t *payload = packet + 188 - payloadSize; + + if (pid == 0) { + extract_pat(&m_pat, payload, payloadSize, unitStart, counter); + auto itFirstPmt = std::find_if(m_pat.pmt.begin(), m_pat.pmt.end(), [](const PMT_REF &pmt) { return pmt.program_number != 0; }); + if (m_firstPmtPid != 0 && (itFirstPmt == m_pat.pmt.end() || itFirstPmt->pmt_pid != m_firstPmtPid)) { + m_firstPmtPid = 0; + static const PSI zeroPsi = {}; + m_firstPmtPsi = zeroPsi; + } + if (itFirstPmt != m_pat.pmt.end()) { + m_firstPmtPid = itFirstPmt->pmt_pid; + } + m_packets.insert(m_packets.end(), packet, packet + 188); + } + else if (pid == m_firstPmtPid) { + int done; + do { + done = extract_psi(&m_firstPmtPsi, payload, payloadSize, unitStart, counter); + if (m_firstPmtPsi.version_number && m_firstPmtPsi.table_id == 2) { + AddPmt(pid, m_firstPmtPsi); + } + } + while (!done); + } + else if (pid == m_pcrPid) { + if (adaptation & 2) { + int adaptationLength = packet[4]; + if (adaptationLength >= 6 && !!(packet[5] & 0x10)) { + m_pcr = (packet[10] >> 7) | + (packet[9] << 1) | + (packet[8] << 9) | + (packet[7] << 17) | + (static_cast(packet[6]) << 25); + } + } + m_packets.insert(m_packets.end(), packet, packet + 188); + } + else if (m_removePidSet.count(pid)) { + if (pid == m_captionPid || pid == m_superimposePid) { + auto &pesPair = pid == m_captionPid ? m_captionPes : m_superimposePes; + int &pesCounter = pesPair.first; + std::vector &pes = pesPair.second; + if (unitStart) { + pesCounter = counter; + pes.assign(payload, payload + payloadSize); + } + else if (!pes.empty()) { + pesCounter = (pesCounter + 1) & 0x0f; + if (pesCounter == counter) { + pes.insert(pes.end(), payload, payload + payloadSize); + } + else { + // Ignore packets until the next unit-start + pes.clear(); + } + } + if (pes.size() >= 6) { + size_t pesPacketLength = (pes[4] << 8) | pes[5]; + if (pes.size() >= 6 + pesPacketLength) { + // PES has been accumulated + pes.resize(6 + pesPacketLength); + CheckPrivateDataPes(pes); + pes.clear(); + } + } + } + } + else { + m_packets.insert(m_packets.end(), packet, packet + 188); + } +} + +void CID3Converter::AddPmt(int pid, const PSI &psi) +{ + const uint8_t PES_PRIVATE_DATA = 0x06; + + if (psi.section_length < 9) { + return; + } + const uint8_t *table = psi.data; + int serviceID = (table[3] << 8) | table[4]; + m_pcrPid = ((table[8] & 0x1f) << 8) | table[9]; + if (m_pcrPid == 0x1fff) { + m_pcr = -1; + } + int programInfoLength = ((table[10] & 0x03) << 8) | table[11]; + int pos = 3 + 9 + programInfoLength; + if (psi.section_length < pos) { + return; + } + + // Create PMT + m_buf.clear(); + m_buf.push_back(0); + m_buf.insert(m_buf.end(), table, table + pos); + + int captionPids[2] = {}; + int superimposePids[2] = {}; + int minRemovePid = 0x2000; + m_removePidSet.clear(); + int tableLen = 3 + psi.section_length - 4/*CRC32*/; + while (pos + 4 < tableLen) { + int streamType = table[pos]; + int esPid = ((table[pos + 1] & 0x1f) << 8) | table[pos + 2]; + int esInfoLength = ((table[pos + 3] & 0x03) << 8) | table[pos + 4]; + if (pos + 5 + esInfoLength <= tableLen) { + int componentTag = 0xff; + for (int i = pos + 5; i + 2 < pos + 5 + esInfoLength; i += 2 + table[i + 1]) { + // stream_identifier_descriptor + if (table[i] == 0x52) { + componentTag = table[i + 2]; + break; + } + } + // ARIB caption/superimpose + if (streamType == PES_PRIVATE_DATA && + (componentTag == 0x30 || componentTag == 0x87 || + componentTag == 0x38 || componentTag == 0x88 || + (componentTag == 0xff && m_treatUnknownPrivateDataAsSuperimpose))) { + if (componentTag == 0x30 || componentTag == 0x87) { + captionPids[componentTag != 0x30] = esPid; + } + else { + superimposePids[componentTag != 0x38] = esPid; + } + // Remove from PMT + m_removePidSet.emplace(esPid); + minRemovePid = std::min(esPid, minRemovePid); + } + else { + // Remain + m_buf.insert(m_buf.end(), table + pos, table + pos + 5 + esInfoLength); + if (m_id3Pid == esPid) { + // Reassign PID, rare case. + m_id3Pid = 0; + } + } + } + pos += 5 + esInfoLength; + } + + // Prioritize "A-Profile" + if (m_captionPid != (captionPids[0] ? captionPids[0] : captionPids[1])) { + m_captionPid = captionPids[0] ? captionPids[0] : captionPids[1]; + m_captionPes.second.clear(); + } + if (m_superimposePid != (superimposePids[0] ? superimposePids[0] : superimposePids[1])) { + m_superimposePid = superimposePids[0] ? superimposePids[0] : superimposePids[1]; + m_superimposePes.second.clear(); + } + + if (m_id3Pid == 0 && minRemovePid < 0x2000) { + m_id3Pid = minRemovePid; + } + if (m_id3Pid != 0) { + // Add ID3 Timed Metadata + static const uint8_t metadataPointerDesc[] = { + 0x26, 15, 0xff, 0xff, 'I', 'D', '3', ' ', 0xff, 'I', 'D', '3', ' ', 0x00, 0x1f, + static_cast(serviceID >> 8), + static_cast(serviceID) + }; + static const uint8_t metadataDesc[] = { + 0x26, 13, 0xff, 0xff, 'I', 'D', '3', ' ', 0xff, 'I', 'D', '3', ' ', 0xff, 0x0f + }; + // Add to 1st descriptor loop + programInfoLength += sizeof(metadataPointerDesc); + if (programInfoLength <= 1023) { + m_buf[11] = static_cast(0xf0 | (programInfoLength >> 8)); + m_buf[12] = static_cast(programInfoLength); + m_buf.insert(m_buf.begin() + 13, metadataPointerDesc, metadataPointerDesc + sizeof(metadataPointerDesc)); + } + // Add to 2nd descriptor loop + m_buf.push_back(0x15); + m_buf.push_back(static_cast(0xe0 | (m_id3Pid >> 8))); + m_buf.push_back(static_cast(m_id3Pid)); + m_buf.push_back(0xf0); + m_buf.push_back(static_cast(sizeof(metadataDesc))); + m_buf.insert(m_buf.end(), metadataDesc, metadataDesc + sizeof(metadataDesc)); + } + m_buf[2] = static_cast((m_buf[2] & 0xf0) | ((m_buf.size() + 4 - 4) >> 8)); + m_buf[3] = static_cast(m_buf.size() + 4 - 4); + uint32_t crc = calc_crc32(m_buf.data() + 1, static_cast(m_buf.size() - 1)); + m_buf.push_back(crc >> 24); + m_buf.push_back((crc >> 16) & 0xff); + m_buf.push_back((crc >> 8) & 0xff); + m_buf.push_back(crc & 0xff); + + // Create TS packets + for (size_t i = 0; i < m_buf.size(); i += 184) { + m_packets.push_back(0x47); + m_packets.push_back(static_cast((i == 0 ? 0x40 : 0) | ((pid >> 8) & 0x1f))); + m_packets.push_back(static_cast(pid)); + m_pmtCounter = (m_pmtCounter + 1) & 0x0f; + m_packets.push_back(0x10 | m_pmtCounter); + m_packets.insert(m_packets.end(), m_buf.begin() + i, m_buf.begin() + std::min(i + 184, m_buf.size())); + m_packets.resize(((m_packets.size() - 1) / 188 + 1) * 188, 0xff); + } +} + +void CID3Converter::CheckPrivateDataPes(const std::vector &pes) +{ + const uint8_t PRIVATE_STREAM_1 = 0xbd; + const uint8_t PRIVATE_STREAM_2 = 0xbf; + const int ACCEPTABLE_PTS_DIFF_SEC = 10; + + size_t payloadPos = 0; + int64_t pts = -1; + if (pes[0] == 0 && pes[1] == 0 && pes[2] == 1) { + int streamID = pes[3]; + if (streamID == PRIVATE_STREAM_1 && pes.size() >= 9) { + int ptsDtsFlags = pes[7] >> 6; + payloadPos = 9 + pes[8]; + if (ptsDtsFlags >= 2 && pes.size() >= 14) { + pts = (pes[13] >> 1) | + (pes[12] << 7) | + ((pes[11] & 0xfe) << 14) | + (pes[10] << 22) | + (static_cast(pes[9] & 0x0e) << 29); + } + } + else if (streamID == PRIVATE_STREAM_2) { + payloadPos = 6; + if (m_pcr >= 0) { + pts = m_pcr; + } + } + } + if (payloadPos == 0 || payloadPos + 1 >= pes.size() || pts < 0) { + return; + } + int dataIdentifier = pes[payloadPos]; + int privateStreamID = pes[payloadPos + 1]; + if ((dataIdentifier != 0x80 && dataIdentifier != 0x81) || + privateStreamID != 0xff) { + // Not an ARIB Synchronized/Asynchronous PES data + return; + } + if (m_forceMonotonousPts) { + if (m_lastID3Pts >= 0 && + ((0x200000000 + m_lastID3Pts - pts) & 0x1ffffffff) < 90000 * ACCEPTABLE_PTS_DIFF_SEC) { + // Prevent PTS goes back + pts = m_lastID3Pts; + } + m_lastID3Pts = pts; + } + + // ID3 Timed Metadata + m_buf.clear(); + m_buf.push_back(0); + m_buf.push_back(0); + m_buf.push_back(1); + m_buf.push_back(PRIVATE_STREAM_1); + m_buf.resize(m_buf.size() + 2); // PES length + m_buf.push_back(0x80); + m_buf.push_back(0x80); + m_buf.push_back(5); + m_buf.push_back(static_cast(pts >> 29) | 0x21); // 3 bits + m_buf.push_back(static_cast(pts >> 22)); // 8 bits + m_buf.push_back(static_cast(pts >> 14) | 1); // 7 bits + m_buf.push_back(static_cast(pts >> 7)); // 8 bits + m_buf.push_back(static_cast(pts << 1) | 1); // 7 bits + if (m_insertInappropriate5BytesIntoPesPayload) { + m_buf.insert(m_buf.end(), 5, 0); + } + m_buf.push_back('I'); + m_buf.push_back('D'); + m_buf.push_back('3'); + m_buf.push_back(4); + m_buf.push_back(0); + m_buf.push_back(0x00); + m_buf.resize(m_buf.size() + 4); // ID3 frame length + size_t privFramePos = m_buf.size(); + m_buf.push_back('P'); + m_buf.push_back('R'); + m_buf.push_back('I'); + m_buf.push_back('V'); + m_buf.resize(m_buf.size() + 4); // PRIV frame length + m_buf.push_back(0); + m_buf.push_back(0); + size_t privPayloadPos = m_buf.size(); + m_buf.push_back('a'); + m_buf.push_back('r'); + m_buf.push_back('i'); + m_buf.push_back('b'); + m_buf.push_back('b'); + m_buf.push_back('2'); + m_buf.push_back('4'); + m_buf.push_back('.'); + m_buf.push_back('j'); + m_buf.push_back('s'); + m_buf.push_back(0); + m_buf.insert(m_buf.end(), pes.begin() + payloadPos, pes.end()); + + // Set length fields + size_t privLen = m_buf.size() - privPayloadPos; + m_buf[privPayloadPos - 6] = (privLen >> 21) & 0x7f; + m_buf[privPayloadPos - 5] = (privLen >> 14) & 0x7f; + m_buf[privPayloadPos - 4] = (privLen >> 7) & 0x7f; + m_buf[privPayloadPos - 3] = privLen & 0x7f; + size_t id3Len = m_buf.size() - privFramePos; + m_buf[privFramePos - 4] = (id3Len >> 21) & 0x7f; + m_buf[privFramePos - 3] = (id3Len >> 14) & 0x7f; + m_buf[privFramePos - 2] = (id3Len >> 7) & 0x7f; + m_buf[privFramePos - 1] = id3Len & 0x7f; + size_t pesLen = m_buf.size() - 6; + m_buf[4] = static_cast(pesLen >> 8); + m_buf[5] = static_cast(pesLen); + + // Create TS packets + for (size_t i = 0; i < m_buf.size(); i += 184) { + m_packets.push_back(0x47); + m_packets.push_back(static_cast((i == 0 ? 0x40 : 0) | ((m_id3Pid >> 8) & 0x1f))); + m_packets.push_back(static_cast(m_id3Pid)); + m_id3Counter = (m_id3Counter + 1) & 0x0f; + size_t len = std::min(184, m_buf.size() - i); + m_packets.push_back((len < 184 ? 0x30 : 0x10) | m_id3Counter); + if (len < 184) { + m_packets.push_back(static_cast(183 - len)); + if (len < 183) { + m_packets.push_back(0x00); + m_packets.insert(m_packets.end(), 182 - len, 0xff); + } + } + m_packets.insert(m_packets.end(), m_buf.begin() + i, m_buf.begin() + i + len); + } +} diff --git a/tsreadex/id3conv.hpp b/tsreadex/id3conv.hpp new file mode 100644 index 0000000..3278f8c --- /dev/null +++ b/tsreadex/id3conv.hpp @@ -0,0 +1,45 @@ +#ifndef INCLUDE_ID3CONV_HPP +#define INCLUDE_ID3CONV_HPP + +#include "util.hpp" +#include +#include +#include +#include + +class CID3Converter +{ +public: + CID3Converter(); + void AddPacket(const uint8_t *packet); + void SetOption(int flags); + const std::vector &GetPackets() const { return m_packets; } + void ClearPackets() { m_packets.clear(); } + +private: + void AddPmt(int pid, const PSI &psi); + void CheckPrivateDataPes(const std::vector &pes); + + bool m_enabled; + bool m_treatUnknownPrivateDataAsSuperimpose; + bool m_insertInappropriate5BytesIntoPesPayload; + bool m_forceMonotonousPts; + int64_t m_lastID3Pts; + std::vector m_packets; + PAT m_pat; + int m_firstPmtPid; + PSI m_firstPmtPsi; + std::unordered_set m_removePidSet; + int m_captionPid; + int m_superimposePid; + std::pair> m_captionPes; + std::pair> m_superimposePes; + int m_pcrPid; + int64_t m_pcr; + int m_id3Pid; + uint8_t m_id3Counter; + uint8_t m_pmtCounter; + std::vector m_buf; +}; + +#endif diff --git a/tsreadex/maketree.cpp b/tsreadex/maketree.cpp new file mode 100644 index 0000000..3be3dd3 --- /dev/null +++ b/tsreadex/maketree.cpp @@ -0,0 +1,155 @@ +#include +#include +#include +#include +#include + +namespace +{ +// ISO/IEC 13818-7 Huffman Codebook Tables + +const uint32_t SCALEFACTOR_TABLE[][2] +{ + {0x3ffe8, 18}, + ... +}; + +const uint32_t SPECTRUM1_TABLE[][2] +{ + {0x7f8, 11}, + ... +}; + +const uint32_t SPECTRUM2_TABLE[][2] +{ + ... +}; + +const uint32_t SPECTRUM3_TABLE[][2] +{ + ... +}; + +const uint32_t SPECTRUM4_TABLE[][2] +{ + ... +}; + +const uint32_t SPECTRUM5_TABLE[][2] +{ + ... +}; + +const uint32_t SPECTRUM6_TABLE[][2] +{ + ... +}; + +const uint32_t SPECTRUM7_TABLE[][2] +{ + ... +}; + +const uint32_t SPECTRUM8_TABLE[][2] +{ + ... +}; + +const uint32_t SPECTRUM9_TABLE[][2] +{ + ... +}; + +const uint32_t SPECTRUM10_TABLE[][2] +{ + ... +}; + +const uint32_t SPECTRUM11_TABLE[][2] +{ + ... +}; + +const uint32_t (*const CODEBOOK_TABLES[])[2] +{ + SCALEFACTOR_TABLE, + SPECTRUM1_TABLE, + SPECTRUM2_TABLE, + SPECTRUM3_TABLE, + SPECTRUM4_TABLE, + SPECTRUM5_TABLE, + SPECTRUM6_TABLE, + SPECTRUM7_TABLE, + SPECTRUM8_TABLE, + SPECTRUM9_TABLE, + SPECTRUM10_TABLE, + SPECTRUM11_TABLE, +}; + +const int CODEBOOK_TABLE_LENS[] +{ + sizeof(SCALEFACTOR_TABLE) / sizeof(SCALEFACTOR_TABLE[0]), + sizeof(SPECTRUM1_TABLE) / sizeof(SPECTRUM1_TABLE[0]), + sizeof(SPECTRUM2_TABLE) / sizeof(SPECTRUM2_TABLE[0]), + sizeof(SPECTRUM3_TABLE) / sizeof(SPECTRUM3_TABLE[0]), + sizeof(SPECTRUM4_TABLE) / sizeof(SPECTRUM4_TABLE[0]), + sizeof(SPECTRUM5_TABLE) / sizeof(SPECTRUM5_TABLE[0]), + sizeof(SPECTRUM6_TABLE) / sizeof(SPECTRUM6_TABLE[0]), + sizeof(SPECTRUM7_TABLE) / sizeof(SPECTRUM7_TABLE[0]), + sizeof(SPECTRUM8_TABLE) / sizeof(SPECTRUM8_TABLE[0]), + sizeof(SPECTRUM9_TABLE) / sizeof(SPECTRUM9_TABLE[0]), + sizeof(SPECTRUM10_TABLE) / sizeof(SPECTRUM10_TABLE[0]), + sizeof(SPECTRUM11_TABLE) / sizeof(SPECTRUM11_TABLE[0]), +}; + +const int CODEBOOK_NUM = 12; + +void AppendCodeword(std::vector> &tree, int codeIndex, uint32_t codeword, uint32_t len) +{ + int current = 0; + while (len > 0) { + // 0: unset, <500: node, else: leaf + int &next = (codeword >> (--len)) & 1 ? tree[current].second : tree[current].first; + if (next == 0) { + if (len == 0) { + next = 500 + codeIndex; + } + else { + current = next = static_cast(tree.size()); + tree.emplace_back(0, 0); + } + } + else if (next >= 500) { + throw std::runtime_error("leaf overwritten"); + } + else { + current = next; + } + } +} +} + +int main() +{ + int maxLen = 0; + for (int i = 0; i < CODEBOOK_NUM; ++i) { + std::vector> tree; + tree.emplace_back(0, 0); + for (int j = 0; j < CODEBOOK_TABLE_LENS[i]; ++j) { + AppendCodeword(tree, j, CODEBOOK_TABLES[i][j][0], CODEBOOK_TABLES[i][j][1]); + if (maxLen < CODEBOOK_TABLES[i][j][1]) { + maxLen = CODEBOOK_TABLES[i][j][1]; + } + } + printf("const uint16_t SPECTRUM%d_TREE[][2] =\n{", i); + for (size_t j = 0; j < tree.size(); ++j) { + if (tree[j].first == 0 || tree[j].second == 0) { + throw std::runtime_error("tree has unset node"); + } + printf("%s{%3d, %3d},", j % 5 ? " " : "\n ", tree[j].first, tree[j].second); + } + printf("\n};\n\n"); + } + printf("const size_t MAX_CODEWORD_LEN = %d;\n", maxLen); + return 0; +} diff --git a/tsreadex/servicefilter.cpp b/tsreadex/servicefilter.cpp new file mode 100644 index 0000000..000d8ad --- /dev/null +++ b/tsreadex/servicefilter.cpp @@ -0,0 +1,988 @@ +#include "servicefilter.hpp" +#include "aac.hpp" +#include + +CServiceFilter::CServiceFilter() + : m_programNumberOrIndex(0) + , m_audio1Mode(0) + , m_audio2Mode(0) + , m_audio1MuxToStereo(false) + , m_audio2MuxToStereo(false) + , m_audio1MuxDualMono(false) + , m_captionMode(0) + , m_superimposeMode(0) + , m_captionInsertManagementPacket(false) + , m_superimposeInsertManagementPacket(false) + , m_videoPid(0) + , m_audio1Pid(0) + , m_audio2Pid(0) + , m_audio1StreamType(0) + , m_audio2StreamType(0) + , m_captionPid(0) + , m_superimposePid(0) + , m_pcrPid(0) + , m_pcr(-1) + , m_patCounter(0) + , m_pmtCounter(0) + , m_audio1PesCounter(0) + , m_audio2PesCounter(0) + , m_captionPesCounter(0xff) + , m_superimposePesCounter(0xff) + , m_isAudio1DualMono(false) + , m_audio1Pts(-1) + , m_audio2Pts(-1) + , m_audio1PtsPcrDiff(0) + , m_audio2PtsPcrDiff(-1) + , m_captionManagementPcr(-1) + , m_superimposeManagementPcr(-1) +{ + static const PAT zeroPat = {}; + m_pat = zeroPat; + m_pmtPsi = zeroPat.psi; +} + +void CServiceFilter::SetAudio1Mode(int mode) +{ + m_audio1Mode = mode % 4; + m_audio1MuxToStereo = !!(mode & 4); + m_audio1MuxDualMono = !!(mode & 8); +} + +void CServiceFilter::SetAudio2Mode(int mode) +{ + m_audio2Mode = mode % 4; + m_audio2MuxToStereo = !!(mode & 4); +} + +void CServiceFilter::SetCaptionMode(int mode) +{ + m_captionMode = mode % 4; + m_captionInsertManagementPacket = !!(mode & 4); +} + +void CServiceFilter::SetSuperimposeMode(int mode) +{ + m_superimposeMode = mode % 4; + m_superimposeInsertManagementPacket = !!(mode & 4); +} + +void CServiceFilter::AddPacket(const uint8_t *packet) +{ + if (m_programNumberOrIndex == 0) { + m_packets.insert(m_packets.end(), packet, packet + 188); + return; + } + + int unitStart = extract_ts_header_unit_start(packet); + int pid = extract_ts_header_pid(packet); + int adaptation = extract_ts_header_adaptation(packet); + int counter = extract_ts_header_counter(packet); + int payloadSize = get_ts_payload_size(packet); + const uint8_t *payload = packet + 188 - payloadSize; + + if (pid == 0) { + extract_pat(&m_pat, payload, payloadSize, unitStart, counter); + auto itPmt = FindTargetPmtRef(m_pat.pmt); + if (itPmt != m_pat.pmt.end()) { + if (unitStart) { + AddPat(m_pat.transport_stream_id, itPmt->program_number, FindNitRef(m_pat.pmt) != m_pat.pmt.end()); + } + } + else { + m_videoPid = 0; + m_audio1Pid = 0; + m_audio2Pid = 0; + m_captionPid = 0; + m_superimposePid = 0; + m_pcrPid = 0; + m_pcr = -1; + } + } + else { + auto itPmt = FindTargetPmtRef(m_pat.pmt); + if (itPmt != m_pat.pmt.end()) { + if (pid == itPmt->pmt_pid) { + int done; + do { + done = extract_psi(&m_pmtPsi, payload, payloadSize, unitStart, counter); + if (m_pmtPsi.version_number && m_pmtPsi.table_id == 2 && m_pmtPsi.current_next_indicator) { + AddPmt(m_pmtPsi); + } + } + while (!done); + } + if (pid == m_pcrPid) { + if (adaptation & 2) { + int adaptationLength = packet[4]; + if (adaptationLength >= 6 && !!(packet[5] & 0x10)) { + if (pid != m_videoPid && + pid != m_audio1Pid && + pid != m_audio2Pid && + pid != m_captionPid && + pid != m_superimposePid) { + AddPcrAdaptation(packet + 6); + } + m_pcr = (packet[10] >> 7) | + (packet[9] << 1) | + (packet[8] << 9) | + (packet[7] << 17) | + (static_cast(packet[6]) << 25); + if (m_audio1Mode == 1 && m_audio1Pid == 0) { + AddAudioPesPackets(0, (m_pcr + m_audio1PtsPcrDiff) & 0x1ffffffff, m_audio1Pts, m_audio1PesCounter); + } + if ((m_audio2Mode == 1 || (m_audio2Mode == 3 && m_audio1Pid == 0)) && m_audio2Pid == 0 && !m_isAudio1DualMono) { + if (m_audio2PtsPcrDiff < 0) { + m_audio2PtsPcrDiff = m_audio1PtsPcrDiff; + } + AddAudioPesPackets(1, (m_pcr + m_audio2PtsPcrDiff) & 0x1ffffffff, m_audio2Pts, m_audio2PesCounter); + } + + static const int INSERT_MANAGEMENT_DETERMINE_ABSENCE_SEC = 15; + static const int INSERT_MANAGEMENT_INTERVAL_SEC = INSERT_MANAGEMENT_DETERMINE_ABSENCE_SEC - 5; + if (m_captionManagementPcr >= 0 && + m_captionInsertManagementPacket && + (m_captionPid != 0 || m_captionMode == 1)) { + int64_t pcrDiff = (0x200000000 + m_pcr - m_captionManagementPcr) & 0x1ffffffff; + if (pcrDiff > 90000 * INSERT_MANAGEMENT_DETERMINE_ABSENCE_SEC) { + if (pcrDiff < 90000 * INSERT_MANAGEMENT_DETERMINE_ABSENCE_SEC * 2) { + m_captionPesCounter = (m_captionPesCounter + 1) & 0x0f; + AddCaptionManagementPesPacket(m_pcr, m_captionPesCounter); + } + m_captionManagementPcr = (0x200000000 + m_pcr - 90000 * INSERT_MANAGEMENT_INTERVAL_SEC) & 0x1ffffffff; + } + } + else { + m_captionManagementPcr = m_pcr; + } + if (m_superimposeManagementPcr >= 0 && + m_superimposeInsertManagementPacket && + (m_superimposePid != 0 || m_superimposeMode == 1)) { + int64_t pcrDiff = (0x200000000 + m_pcr - m_superimposeManagementPcr) & 0x1ffffffff; + if (pcrDiff > 90000 * INSERT_MANAGEMENT_DETERMINE_ABSENCE_SEC) { + if (pcrDiff < 90000 * INSERT_MANAGEMENT_DETERMINE_ABSENCE_SEC * 2) { + m_superimposePesCounter = (m_superimposePesCounter + 1) & 0x0f; + AddSuperimposeManagementPesPacket(m_superimposePesCounter); + } + m_superimposeManagementPcr = (0x200000000 + m_pcr - 90000 * INSERT_MANAGEMENT_INTERVAL_SEC) & 0x1ffffffff; + } + } + else { + m_superimposeManagementPcr = m_pcr; + } + } + } + } + if (pid == m_videoPid) { + ChangePidAndAddPacket(packet, 0x0100); + } + else if (pid == m_audio1Pid) { + if (AccumulatePesPackets(m_audio1UnitPackets, packet, unitStart)) { + bool passthroughAudio1 = false; + bool copyToAudio2 = false; + m_isAudio1DualMono = m_audio1MuxDualMono && m_audio1StreamType == ADTS_TRANSPORT && TransmuxDualMono(m_audio1UnitPackets); + if (m_isAudio1DualMono) { + // Already added + m_audio1UnitPackets.clear(); + } + else { + passthroughAudio1 = !m_audio1MuxToStereo || m_audio1StreamType != ADTS_TRANSPORT || + !TransmuxMonoToStereo(m_audio1UnitPackets, m_audio1MuxWorkspace, 0x0110, m_audio1PesCounter, m_audio1PtsPcrDiff); + // Copy audio1 to audio2 if needed + copyToAudio2 = m_audio2Mode == 3 && m_audio2Pid == 0; + if (copyToAudio2 && m_audio2MuxToStereo && m_audio1StreamType == ADTS_TRANSPORT && + TransmuxMonoToStereo(m_audio1UnitPackets, m_audio2MuxWorkspace, 0x0111, m_audio2PesCounter, m_audio2PtsPcrDiff)) { + // Already added + copyToAudio2 = false; + } + if (!passthroughAudio1 && !copyToAudio2) { + m_audio1UnitPackets.clear(); + } + } + // Add packets + for (size_t i = 0; i + 188 <= m_audio1UnitPackets.size(); i += 188) { + const uint8_t *packet_ = m_audio1UnitPackets.data() + i; + int payloadSize_ = get_ts_payload_size(packet_); + const uint8_t *payload_ = packet_ + 188 - payloadSize_; + int64_t pts = GetAudioPresentationTimeStamp(i == 0, payload_, payloadSize_); + if (passthroughAudio1) { + if (pts >= 0 && m_pcr >= 0) { + m_audio1PtsPcrDiff = 0x200000000 + pts - m_pcr; + } + m_audio1PesCounter = (m_audio1PesCounter + 1) & 0x0f; + ChangePidAndAddPacket(packet_, 0x0110, m_audio1PesCounter); + } + if (copyToAudio2) { + // Copy audio1 to audio2 + if (pts >= 0 && m_pcr >= 0) { + m_audio2PtsPcrDiff = 0x200000000 + pts - m_pcr; + } + m_audio2PesCounter = (m_audio2PesCounter + 1) & 0x0f; + ChangePidAndAddPacket(packet_, 0x0111, m_audio2PesCounter); + } + } + m_audio1UnitPackets.clear(); + } + } + else if (pid == m_audio2Pid) { + if (AccumulatePesPackets(m_audio2UnitPackets, packet, unitStart)) { + if (m_audio2MuxToStereo && m_audio2StreamType == ADTS_TRANSPORT && + TransmuxMonoToStereo(m_audio2UnitPackets, m_audio2MuxWorkspace, 0x0111, m_audio2PesCounter, m_audio2PtsPcrDiff)) { + // Already added + m_audio2UnitPackets.clear(); + } + // Add packets + for (size_t i = 0; i + 188 <= m_audio2UnitPackets.size(); i += 188) { + const uint8_t *packet_ = m_audio2UnitPackets.data() + i; + int payloadSize_ = get_ts_payload_size(packet_); + const uint8_t *payload_ = packet_ + 188 - payloadSize_; + int64_t pts = GetAudioPresentationTimeStamp(i == 0, payload_, payloadSize_); + if (pts >= 0 && m_pcr >= 0) { + m_audio2PtsPcrDiff = 0x200000000 + pts - m_pcr; + } + m_audio2PesCounter = (m_audio2PesCounter + 1) & 0x0f; + ChangePidAndAddPacket(packet_, 0x0111, m_audio2PesCounter); + } + m_audio2UnitPackets.clear(); + } + } + else if (pid == m_captionPid) { + m_captionManagementPcr = m_pcr; + m_captionPesCounter = m_captionPesCounter > 0x0f ? 0x10 | (counter & 0x0f) : (m_captionPesCounter + 1) & 0x0f; + ChangePidAndAddPacket(packet, 0x0130, m_captionPesCounter & 0x0f); + } + else if (pid == m_superimposePid) { + m_superimposeManagementPcr = m_pcr; + m_superimposePesCounter = m_superimposePesCounter > 0x0f ? 0x10 | (counter & 0x0f) : (m_superimposePesCounter + 1) & 0x0f; + ChangePidAndAddPacket(packet, 0x0138, m_superimposePesCounter & 0x0f); + } + else if (pid < 0x0030) { + m_packets.insert(m_packets.end(), packet, packet + 188); + } + else { + auto itNit = FindNitRef(m_pat.pmt); + if (itNit != m_pat.pmt.end() && pid == itNit->pmt_pid) { + // NIT pid should be 0x0010. This case is unusual. + ChangePidAndAddPacket(packet, 0x0010); + } + } + } + } +} + +std::vector::const_iterator CServiceFilter::FindNitRef(const std::vector &pmt) +{ + return std::find_if(pmt.begin(), pmt.end(), [](const PMT_REF &a) { return a.program_number == 0; }); +} + +std::vector::const_iterator CServiceFilter::FindTargetPmtRef(const std::vector &pmt) const +{ + if (m_programNumberOrIndex < 0) { + int index = -m_programNumberOrIndex; + for (auto it = pmt.begin(); it != pmt.end(); ++it) { + if (it->program_number != 0) { + if (--index == 0) { + return it; + } + } + } + return pmt.end(); + } + return std::find_if(pmt.begin(), pmt.end(), [=](const PMT_REF &a) { return a.program_number == m_programNumberOrIndex; }); +} + +void CServiceFilter::AddPat(int transportStreamID, int programNumber, bool addNit) +{ + // Create PAT + m_buf.assign(9, 0); + m_buf[1] = 0x00; + m_buf[2] = 0xb0; + m_buf[3] = addNit ? 17 : 13; + m_buf[4] = static_cast(transportStreamID >> 8); + m_buf[5] = static_cast(transportStreamID); + m_buf[6] = m_lastPat.size() > 6 ? m_lastPat[6] : 0xc1; + if (addNit) { + m_buf.push_back(0); + m_buf.push_back(0); + m_buf.push_back(0xe0); + m_buf.push_back(0x10); + } + m_buf.push_back(static_cast(programNumber >> 8)); + m_buf.push_back(static_cast(programNumber)); + // PMT_PID=0x01f0 + m_buf.push_back(0xe1); + m_buf.push_back(0xf0); + if (m_lastPat.size() == m_buf.size() + 4 && + std::equal(m_buf.begin(), m_buf.end(), m_lastPat.begin())) { + // Copy CRC + m_buf.insert(m_buf.end(), m_lastPat.end() - 4, m_lastPat.end()); + } + else { + // Increment version number + m_buf[6] = 0xc1 | (((m_buf[6] >> 1) + 1) & 0x1f) << 1; + uint32_t crc = calc_crc32(m_buf.data() + 1, static_cast(m_buf.size() - 1)); + m_buf.push_back(crc >> 24); + m_buf.push_back((crc >> 16) & 0xff); + m_buf.push_back((crc >> 8) & 0xff); + m_buf.push_back(crc & 0xff); + m_lastPat = m_buf; + } + + // Create TS packet + m_packets.push_back(0x47); + m_packets.push_back(0x40); + m_packets.push_back(0x00); + m_patCounter = (m_patCounter + 1) & 0x0f; + m_packets.push_back(0x10 | m_patCounter); + m_packets.insert(m_packets.end(), m_buf.begin(), m_buf.end()); + m_packets.resize((m_packets.size() / 188 + 1) * 188, 0xff); +} + +void CServiceFilter::AddPmt(const PSI &psi) +{ + if (psi.section_length < 9) { + return; + } + const uint8_t *table = psi.data; + int programNumber = (table[3] << 8) | table[4]; + m_pcrPid = ((table[8] & 0x1f) << 8) | table[9]; + if (m_pcrPid == 0x1fff) { + m_pcr = -1; + } + int programInfoLength = ((table[10] & 0x03) << 8) | table[11]; + int pos = 3 + 9 + programInfoLength; + if (psi.section_length < pos) { + return; + } + + // Create PMT + m_buf.assign(13, 0); + m_buf[1] = 0x02; + m_buf[4] = static_cast(programNumber >> 8); + m_buf[5] = static_cast(programNumber); + m_buf[6] = m_lastPmt.size() > 6 ? m_lastPmt[6] : 0xc1; + // PCR_PID=0x01ff + m_buf[9] = 0xe1; + m_buf[10] = 0xff; + m_buf[11] = 0xf0 | static_cast(programInfoLength >> 8); + m_buf[12] = static_cast(programInfoLength); + // Copy 1st descriptor loop + m_buf.insert(m_buf.end(), table + 12, table + pos); + + int lastAudio1Pid = m_audio1Pid; + int lastAudio2Pid = m_audio2Pid; + m_videoPid = 0; + m_audio1Pid = 0; + m_audio2Pid = 0; + m_captionPid = 0; + m_superimposePid = 0; + m_audio1StreamType = ADTS_TRANSPORT; + m_audio2StreamType = ADTS_TRANSPORT; + int videoDescPos = 0; + int audio1DescPos = 0; + int audio2DescPos = 0; + int captionDescPos = 0; + int superimposeDescPos = 0; + bool maybeCProfile = false; + bool audio1ComponentTagUnknown = true; + + int tableLen = 3 + psi.section_length - 4/*CRC32*/; + while (pos + 4 < tableLen) { + uint8_t streamType = table[pos]; + int esPid = ((table[pos + 1] & 0x1f) << 8) | table[pos + 2]; + int esInfoLength = ((table[pos + 3] & 0x03) << 8) | table[pos + 4]; + if (pos + 5 + esInfoLength <= tableLen) { + int componentTag = 0xff; + for (int i = pos + 5; i + 2 < pos + 5 + esInfoLength; i += 2 + table[i + 1]) { + // stream_identifier_descriptor + if (table[i] == 0x52) { + componentTag = table[i + 2]; + break; + } + } + if (streamType == H_262_VIDEO || + streamType == AVC_VIDEO || + streamType == H_265_VIDEO) { + if ((m_videoPid == 0 && componentTag == 0xff) || componentTag == 0x00 || componentTag == 0x81) { + m_videoPid = esPid; + videoDescPos = pos; + maybeCProfile = componentTag == 0x81; + } + } + else if (streamType == ADTS_TRANSPORT) { + if ((m_audio1Pid == 0 && componentTag == 0xff) || componentTag == 0x10 || componentTag == 0x83 || componentTag == 0x85) { + m_audio1Pid = esPid; + m_audio1StreamType = streamType; + audio1DescPos = pos; + audio1ComponentTagUnknown = componentTag == 0xff; + } + else if (componentTag == 0x11) { + if (m_audio2Mode != 2) { + m_audio2Pid = esPid; + m_audio2StreamType = streamType; + audio2DescPos = pos; + } + } + } + else if (streamType == MPEG2_AUDIO) { + if (m_audio1Pid == 0) { + m_audio1Pid = esPid; + m_audio1StreamType = streamType; + audio1DescPos = pos; + audio1ComponentTagUnknown = false; + } + else if (m_audio2Pid == 0) { + if (m_audio2Mode != 2) { + m_audio2Pid = esPid; + m_audio2StreamType = streamType; + audio2DescPos = pos; + } + } + } + else if (streamType == PES_PRIVATE_DATA) { + if (componentTag == 0x30 || componentTag == 0x87) { + if (m_captionMode != 2) { + m_captionPid = esPid; + captionDescPos = pos; + } + } + else if (componentTag == 0x38 || componentTag == 0x88) { + if (m_superimposeMode != 2) { + m_superimposePid = esPid; + superimposeDescPos = pos; + } + } + } + } + pos += 5 + esInfoLength; + } + + if (m_audio1Pid != lastAudio1Pid) { + m_audio1Pts = -1; + m_isAudio1DualMono = false; + m_audio1UnitPackets.clear(); + m_audio1MuxWorkspace.clear(); + m_audio1MuxDualMonoWorkspace.clear(); + } + if (m_audio2Pid != lastAudio2Pid) { + m_audio2Pts = -1; + m_audio2UnitPackets.clear(); + m_audio2MuxWorkspace.clear(); + } + + if (m_videoPid != 0) { + m_buf.push_back(table[videoDescPos]); + // PID=0x0100 + m_buf.push_back(0xe1); + m_buf.push_back(0x00); + int esInfoLength = ((table[videoDescPos + 3] & 0x03) << 8) | table[videoDescPos + 4]; + m_buf.insert(m_buf.end(), table + videoDescPos + 3, table + videoDescPos + 5 + esInfoLength); + if (m_pcrPid == m_videoPid) { + m_buf[9] = 0xe1; + m_buf[10] = 0x00; + } + } + bool addAudio2 = m_audio2Pid != 0 || m_audio2Mode == 1 || m_audio2Mode == 3 || (m_audio2Mode != 2 && m_isAudio1DualMono); + if (m_audio2Mode == 3 && m_audio1Pid != 0 && m_audio2Pid == 0) { + // Copy stream type + m_audio2StreamType = m_audio1StreamType; + } + + if (m_audio1Pid != 0 || m_audio1Mode == 1) { + m_buf.push_back(m_audio1StreamType); + // PID=0x0110 + m_buf.push_back(0xe1); + m_buf.push_back(0x10); + if (m_audio1Pid != 0) { + int esInfoLength = ((table[audio1DescPos + 3] & 0x03) << 8) | table[audio1DescPos + 4]; + if (audio1ComponentTagUnknown && addAudio2) { + int esInfoNewLength = esInfoLength + 3; + m_buf.push_back(0xf0 | static_cast(esInfoNewLength >> 8)); + m_buf.push_back(static_cast(esInfoNewLength)); + m_buf.push_back(0x52); + m_buf.push_back(1); + m_buf.push_back(maybeCProfile ? 0x83 : 0x10); + } + else { + m_buf.push_back(0xf0 | static_cast(esInfoLength >> 8)); + m_buf.push_back(static_cast(esInfoLength)); + } + m_buf.insert(m_buf.end(), table + audio1DescPos + 5, table + audio1DescPos + 5 + esInfoLength); + if (m_pcrPid == m_audio1Pid) { + m_buf[9] = 0xe1; + m_buf[10] = 0x10; + } + } + else { + m_buf.push_back(0xf0); + m_buf.push_back(3); + m_buf.push_back(0x52); + m_buf.push_back(1); + m_buf.push_back(maybeCProfile ? 0x83 : 0x10); + } + } + if (addAudio2) { + m_buf.push_back(m_audio2StreamType); + // PID=0x0111 + m_buf.push_back(0xe1); + m_buf.push_back(0x11); + if (m_audio2Pid != 0) { + int esInfoLength = ((table[audio2DescPos + 3] & 0x03) << 8) | table[audio2DescPos + 4]; + m_buf.insert(m_buf.end(), table + audio2DescPos + 3, table + audio2DescPos + 5 + esInfoLength); + if (m_pcrPid == m_audio2Pid) { + m_buf[9] = 0xe1; + m_buf[10] = 0x11; + } + } + else { + m_buf.push_back(0xf0); + m_buf.push_back(3); + m_buf.push_back(0x52); + m_buf.push_back(1); + m_buf.push_back(maybeCProfile ? 0x85 : 0x11); + } + } + if (m_captionPid != 0 || m_captionMode == 1) { + m_buf.push_back(PES_PRIVATE_DATA); + // PID=0x0130 + m_buf.push_back(0xe1); + m_buf.push_back(0x30); + if (m_captionPid != 0) { + int esInfoLength = ((table[captionDescPos + 3] & 0x03) << 8) | table[captionDescPos + 4]; + m_buf.insert(m_buf.end(), table + captionDescPos + 3, table + captionDescPos + 5 + esInfoLength); + if (m_pcrPid == m_captionPid) { + m_buf[9] = 0xe1; + m_buf[10] = 0x30; + } + } + else { + m_buf.push_back(0xf0); + m_buf.push_back(3 + (maybeCProfile ? 0 : 5)); + m_buf.push_back(0x52); + m_buf.push_back(1); + m_buf.push_back(maybeCProfile ? 0x87 : 0x30); + if (!maybeCProfile) { + // data_component_descriptor + m_buf.push_back(0xfd); + m_buf.push_back(3); + m_buf.push_back(0x00); + m_buf.push_back(0x08); + m_buf.push_back(0x3d); + } + } + } + if (m_superimposePid != 0 || m_superimposeMode == 1) { + m_buf.push_back(PES_PRIVATE_DATA); + // PID=0x0138 + m_buf.push_back(0xe1); + m_buf.push_back(0x38); + if (m_superimposePid != 0) { + int esInfoLength = ((table[superimposeDescPos + 3] & 0x03) << 8) | table[superimposeDescPos + 4]; + m_buf.insert(m_buf.end(), table + superimposeDescPos + 3, table + superimposeDescPos + 5 + esInfoLength); + if (m_pcrPid == m_superimposePid) { + m_buf[9] = 0xe1; + m_buf[10] = 0x38; + } + } + else { + m_buf.push_back(0xf0); + m_buf.push_back(3 + (maybeCProfile ? 0 : 5)); + // component_tag=0x38 + m_buf.push_back(0x52); + m_buf.push_back(1); + m_buf.push_back(maybeCProfile ? 0x88 : 0x38); + if (!maybeCProfile) { + // data_component_descriptor + m_buf.push_back(0xfd); + m_buf.push_back(3); + m_buf.push_back(0x00); + m_buf.push_back(0x08); + m_buf.push_back(0x3c); + } + } + } + + m_buf[2] = 0xb0 | static_cast((m_buf.size() + 4 - 4) >> 8); + m_buf[3] = static_cast(m_buf.size() + 4 - 4); + + if (m_lastPmt.size() == m_buf.size() + 4 && + std::equal(m_buf.begin(), m_buf.end(), m_lastPmt.begin())) { + // Copy CRC + m_buf.insert(m_buf.end(), m_lastPmt.end() - 4, m_lastPmt.end()); + } + else { + // Increment version number + m_buf[6] = 0xc1 | (((m_buf[6] >> 1) + 1) & 0x1f) << 1; + uint32_t crc = calc_crc32(m_buf.data() + 1, static_cast(m_buf.size() - 1)); + m_buf.push_back(crc >> 24); + m_buf.push_back((crc >> 16) & 0xff); + m_buf.push_back((crc >> 8) & 0xff); + m_buf.push_back(crc & 0xff); + m_lastPmt = m_buf; + } + + // Create TS packets + for (size_t i = 0; i < m_buf.size(); i += 184) { + m_packets.push_back(0x47); + // PMT_PID=0x01f0 + m_packets.push_back((i == 0 ? 0x40 : 0) | 0x01); + m_packets.push_back(0xf0); + m_pmtCounter = (m_pmtCounter + 1) & 0x0f; + m_packets.push_back(0x10 | m_pmtCounter); + m_packets.insert(m_packets.end(), m_buf.begin() + i, m_buf.begin() + std::min(i + 184, m_buf.size())); + m_packets.resize(((m_packets.size() - 1) / 188 + 1) * 188, 0xff); + } +} + +void CServiceFilter::AddPcrAdaptation(const uint8_t *pcr) +{ + // Create TS packet + m_packets.push_back(0x47); + // PCR_PID=0x01ff + m_packets.push_back(0x01); + m_packets.push_back(0xff); + m_packets.push_back(0x20); + m_packets.push_back(183); + m_packets.push_back(0x10); + m_packets.insert(m_packets.end(), pcr, pcr + 4); + // pcr_extension=0 + m_packets.push_back((pcr[4] & 0x80) | 0x7e); + m_packets.push_back(0); + m_packets.resize((m_packets.size() / 188 + 1) * 188, 0xff); +} + +void CServiceFilter::ChangePidAndAddPacket(const uint8_t *packet, int pid, uint8_t counter) +{ + m_packets.push_back(0x47); + m_packets.push_back((packet[1] & 0xe0) | static_cast(pid >> 8)); + m_packets.push_back(static_cast(pid)); + m_packets.push_back(counter > 0x0f ? packet[3] : ((packet[3] & 0xf0) | counter)); + m_packets.insert(m_packets.end(), packet + 4, packet + 188); +} + +void CServiceFilter::AddCaptionManagementPesPacket(int64_t pts, uint8_t counter) +{ + static const uint8_t SYNCHRONOUS_PES_JPN_MANAGEMENT[20] = { + 0x80, 0xff, 0xf0, 0x80, 0x00, 0x00, 0x00, 0x0a, + 0x3f, 0x01, 0x1a, 0x6a, 0x70, 0x6e, 0x80, 0x00, 0x00, 0x00, + 0xe4, 0x6a + }; + m_packets.push_back(0x47); + // PID=0x0130 + m_packets.push_back(0x41); + m_packets.push_back(0x30); + m_packets.push_back(0x30 | counter); + m_packets.push_back(188 - 5 - (6 + 28)); + m_packets.push_back(0x00); + // stuffing + m_packets.resize(m_packets.size() + 188 - 6 - (6 + 28), 0xff); + // PES + m_packets.push_back(0); + m_packets.push_back(0); + m_packets.push_back(1); + m_packets.push_back(0xbd); + m_packets.push_back(0); + m_packets.push_back(28); + m_packets.push_back(0x80); + // has PTS + m_packets.push_back(0x80); + m_packets.push_back(5); + m_packets.push_back(static_cast(pts >> 29) | 0x21); // 3 bits + m_packets.push_back(static_cast(pts >> 22)); // 8 bits + m_packets.push_back(static_cast(pts >> 14) | 1); // 7 bits + m_packets.push_back(static_cast(pts >> 7)); // 8 bits + m_packets.push_back(static_cast(pts << 1) | 1); // 7 bits + m_packets.insert(m_packets.end(), SYNCHRONOUS_PES_JPN_MANAGEMENT, SYNCHRONOUS_PES_JPN_MANAGEMENT + 20); +} + +void CServiceFilter::AddSuperimposeManagementPesPacket(uint8_t counter) +{ + static const uint8_t ASYNCHRONOUS_PES_JPN_MANAGEMENT[20] = { + 0x81, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x0a, + 0x3f, 0x01, 0x12, 0x6a, 0x70, 0x6e, 0x80, 0x00, 0x00, 0x00, + 0xae, 0xa2 + }; + m_packets.push_back(0x47); + // PID=0x0138 + m_packets.push_back(0x41); + m_packets.push_back(0x38); + m_packets.push_back(0x30 | counter); + m_packets.push_back(188 - 5 - (6 + 20)); + m_packets.push_back(0x00); + // stuffing + m_packets.resize(m_packets.size() + 188 - 6 - (6 + 20), 0xff); + // PES + m_packets.push_back(0); + m_packets.push_back(0); + m_packets.push_back(1); + m_packets.push_back(0xbf); + m_packets.push_back(0); + m_packets.push_back(20); + m_packets.insert(m_packets.end(), ASYNCHRONOUS_PES_JPN_MANAGEMENT, ASYNCHRONOUS_PES_JPN_MANAGEMENT + 20); +} + +void CServiceFilter::AddAudioPesPackets(uint8_t index, int64_t targetPts, int64_t &pts, uint8_t &counter) +{ + static const int ACCEPTABLE_PTS_DIFF_SEC = 10; + + int64_t ptsDiff = (0x200000000 + targetPts - pts) & 0x1ffffffff; + if (pts < 0 || (90000 * ACCEPTABLE_PTS_DIFF_SEC < ptsDiff && ptsDiff < 0x200000000 - 90000 * ACCEPTABLE_PTS_DIFF_SEC)) { + pts = targetPts; + } + for (;;) { + int64_t nextPts = (pts + 90000 * 64 / 1000) & 0x1ffffffff; + if (((0x200000000 + targetPts - nextPts) & 0x1ffffffff) > 900000) { + break; + } + Add64MsecAudioPesPacket(index, pts, counter); + pts = nextPts; + } +} + +void CServiceFilter::Add64MsecAudioPesPacket(uint8_t index, int64_t pts, uint8_t &counter) +{ + static const uint8_t ADTS_2CH_48KHZ_SILENT[13] = { + 0xff, 0xf1, 0x4c, 0x80, 0x01, 0xbf, 0xfc, 0x21, 0x10, 0x04, 0x60, 0x8c, 0x1c + }; + m_packets.push_back(0x47); + // PID=0x0110+index + m_packets.push_back(0x41); + m_packets.push_back(0x10 | index); + counter = (counter + 1) & 0x0f; + m_packets.push_back(0x30 | counter); + m_packets.push_back(188 - 5 - (6 + 8 + 13 * 3)); + m_packets.push_back(0x40); + // stuffing + m_packets.resize(m_packets.size() + 188 - 6 - (6 + 8 + 13 * 3), 0xff); + // PES + m_packets.push_back(0); + m_packets.push_back(0); + m_packets.push_back(1); + m_packets.push_back(0xc0 | index); + m_packets.push_back(0); + m_packets.push_back(8 + 13 * 3); + // alignment by audio sync word + m_packets.push_back(0x84); + // has PTS + m_packets.push_back(0x80); + m_packets.push_back(5); + m_packets.push_back(static_cast(pts >> 29) | 0x21); // 3 bits + m_packets.push_back(static_cast(pts >> 22)); // 8 bits + m_packets.push_back(static_cast(pts >> 14) | 1); // 7 bits + m_packets.push_back(static_cast(pts >> 7)); // 8 bits + m_packets.push_back(static_cast(pts << 1) | 1); // 7 bits + // 1024samples(1frame) / 48000hz * 3 = 0.064sec + for (int i = 0; i < 3; ++i) { + m_packets.insert(m_packets.end(), ADTS_2CH_48KHZ_SILENT, ADTS_2CH_48KHZ_SILENT + 13); + } +} + +int64_t CServiceFilter::GetAudioPresentationTimeStamp(int unitStart, const uint8_t *payload, int payloadSize) +{ + if (unitStart && payloadSize >= 6 && payload[0] == 0 && payload[1] == 0 && payload[2] == 1) { + int streamID = payload[3]; + size_t pesPacketLength = (payload[4] << 8) | payload[5]; + // audio stream + if ((streamID & 0xe0) == 0xc0 && pesPacketLength >= 3 && payloadSize >= 9) { + int ptsDtsFlags = payload[7] >> 6; + if (ptsDtsFlags >= 2 && pesPacketLength >= 8 && payloadSize >= 14) { + return (payload[13] >> 1) | + (payload[12] << 7) | + ((payload[11] & 0xfe) << 14) | + (payload[10] << 22) | + (static_cast(payload[9] & 0x0e) << 29); + } + } + } + return -1; +} + +bool CServiceFilter::AccumulatePesPackets(std::vector &unitPackets, const uint8_t *packet, int unitStart) +{ + if (unitStart) { + unitPackets.assign(packet, packet + 188); + } + // Cancel accumulations that are not possible for a regular (with a valid length field) PES + else if (!unitPackets.empty() && unitPackets.size() < 0x20000) { + unitPackets.insert(unitPackets.end(), packet, packet + 188); + } + + // Check if PES has accumulated + int lastCounter = -1; + int entireSize = 0; + uint8_t head[6]; + for (size_t i = 0; i + 188 <= unitPackets.size(); i += 188) { + const uint8_t *packet_ = unitPackets.data() + i; + int counter = extract_ts_header_counter(packet_); + if (lastCounter >= 0 && ((lastCounter + 1) & 0x0f) != counter) { + unitPackets.clear(); + return false; + } + lastCounter = counter; + + int payloadSize = get_ts_payload_size(packet_); + const uint8_t *payload = packet_ + 188 - payloadSize; + for (int j = 0; j < payloadSize && entireSize + j < 6; ++j) { + head[entireSize + j] = payload[j]; + } + entireSize += payloadSize; + if (entireSize >= 6) { + if (head[0] != 0 || head[1] != 0 || head[2] != 1) { + unitPackets.clear(); + return false; + } + int pesPacketLength = (head[4] << 8) | head[5]; + if (entireSize >= 6 + pesPacketLength) { + return true; + } + } + } + return false; +} + +void CServiceFilter::ConcatenatePayload(std::vector &dest, const std::vector &unitPackets, bool &pcrFlag, uint8_t (&pcr)[6]) +{ + dest.clear(); + pcrFlag = false; + for (size_t i = 0; i + 188 <= unitPackets.size(); i += 188) { + const uint8_t *packet = unitPackets.data() + i; + int adaptation = extract_ts_header_adaptation(packet); + if (adaptation & 2) { + int adaptationLength = packet[4]; + if (adaptationLength >= 7 && !!(packet[5] & 0x10)) { + pcrFlag = true; + std::copy(packet + 6, packet + 12, pcr); + } + } + int payloadSize = get_ts_payload_size(packet); + const uint8_t *payload = packet + 188 - payloadSize; + dest.insert(dest.end(), payload, payload + payloadSize); + } +} + +void CServiceFilter::AddAudioPesPackets(const std::vector &pes, int pid, uint8_t &counter, int64_t &ptsPcrDiff, const uint8_t *pcr) +{ + for (size_t i = 0; i < pes.size(); ) { + m_packets.push_back(0x47); + m_packets.push_back((i == 0 ? 0x40 : 0) | static_cast(pid >> 8)); + m_packets.push_back(static_cast(pid)); + counter = (counter + 1) & 0x0f; + size_t len = std::min(184, pes.size() - i); + if (pcr && i + len >= pes.size() && len > 176) { + // Reduce payload in order to insert PCR + len = 176; + } + m_packets.push_back((len < 184 ? 0x30 : 0x10) | counter); + if (len < 184) { + m_packets.push_back(static_cast(183 - len)); + if (len < 183) { + if (pcr && len <= 176) { + // Insert PCR + m_packets.push_back(0x10); + m_packets.insert(m_packets.end(), pcr, pcr + 6); + m_packets.insert(m_packets.end(), 176 - len, 0xff); + pcr = nullptr; + } + else { + m_packets.push_back(0x00); + m_packets.insert(m_packets.end(), 182 - len, 0xff); + } + } + } + int64_t pts = GetAudioPresentationTimeStamp(i == 0, pes.data() + i, static_cast(len)); + if (pts >= 0 && m_pcr >= 0) { + ptsPcrDiff = 0x200000000 + pts - m_pcr; + } + m_packets.insert(m_packets.end(), pes.begin() + i, pes.begin() + i + len); + i += len; + } +} + +bool CServiceFilter::TransmuxMonoToStereo(const std::vector &unitPackets, std::vector &workspace, + int pid, uint8_t &counter, int64_t &ptsPcrDiff) +{ + bool pcrFlag; + uint8_t pcr[6]; + ConcatenatePayload(m_buf, unitPackets, pcrFlag, pcr); + + if (m_buf.size() >= 6 && m_buf[0] == 0 && m_buf[1] == 0 && m_buf[2] == 1) { + int streamID = m_buf[3]; + size_t pesPacketLength = (m_buf[4] << 8) | m_buf[5]; + // audio stream + if ((streamID & 0xe0) == 0xc0 && m_buf.size() >= 6 + pesPacketLength && pesPacketLength >= 3) { + // PES has been accumulated + size_t pesPayloadPos = 9 + m_buf[8]; + if (pesPayloadPos < 6 + pesPacketLength) { + m_buf.resize(6 + pesPacketLength); + if (Aac::TransmuxMonoToStereo(m_destLeftBuf, workspace, m_buf.data() + pesPayloadPos, m_buf.size() - pesPayloadPos) && + !m_destLeftBuf.empty()) { + + // Stereo + m_buf.resize(pesPayloadPos); + m_buf.insert(m_buf.end(), m_destLeftBuf.begin(), m_destLeftBuf.end()); + + // Set length fields + size_t pesLen = m_buf.size() - 6; + m_buf[4] = static_cast(pesLen >> 8); + m_buf[5] = static_cast(pesLen); + AddAudioPesPackets(m_buf, pid, counter, ptsPcrDiff, pcrFlag ? pcr : nullptr); + return true; + } + } + } + } + return false; +} + +bool CServiceFilter::TransmuxDualMono(const std::vector &unitPackets) +{ + bool pcrFlag; + uint8_t pcr[6]; + ConcatenatePayload(m_buf, unitPackets, pcrFlag, pcr); + + if (m_buf.size() >= 6 && m_buf[0] == 0 && m_buf[1] == 0 && m_buf[2] == 1) { + int streamID = m_buf[3]; + size_t pesPacketLength = (m_buf[4] << 8) | m_buf[5]; + // audio stream + if ((streamID & 0xe0) == 0xc0 && m_buf.size() >= 6 + pesPacketLength && pesPacketLength >= 3) { + // PES has been accumulated + size_t pesPayloadPos = 9 + m_buf[8]; + if (pesPayloadPos < 6 + pesPacketLength) { + m_buf.resize(6 + pesPacketLength); + if (Aac::TransmuxDualMono(m_destLeftBuf, m_destRightBuf, m_audio1MuxDualMonoWorkspace, + m_audio1MuxToStereo, m_audio2MuxToStereo, + m_buf.data() + pesPayloadPos, m_buf.size() - pesPayloadPos) && + !m_destLeftBuf.empty() && + !m_destRightBuf.empty()) { + + // Dual mono left + m_buf.resize(pesPayloadPos); + m_buf.insert(m_buf.end(), m_destLeftBuf.begin(), m_destLeftBuf.end()); + + // Set stream ID + m_buf[3] = 0xc0; + // Set length fields + size_t pesLen = m_buf.size() - 6; + m_buf[4] = static_cast(pesLen >> 8); + m_buf[5] = static_cast(pesLen); + AddAudioPesPackets(m_buf, 0x0110, m_audio1PesCounter, m_audio1PtsPcrDiff, pcrFlag ? pcr : nullptr); + + if (m_audio2Pid == 0 && m_audio2Mode != 2) { + // Dual mono right + m_buf.resize(pesPayloadPos); + m_buf.insert(m_buf.end(), m_destRightBuf.begin(), m_destRightBuf.end()); + + // Set stream ID + m_buf[3] = 0xc1; + // Set length fields + pesLen = m_buf.size() - 6; + m_buf[4] = static_cast(pesLen >> 8); + m_buf[5] = static_cast(pesLen); + AddAudioPesPackets(m_buf, 0x0111, m_audio2PesCounter, m_audio2PtsPcrDiff, nullptr); + } + return true; + } + } + } + } + return false; +} diff --git a/tsreadex/servicefilter.hpp b/tsreadex/servicefilter.hpp new file mode 100644 index 0000000..e5d351b --- /dev/null +++ b/tsreadex/servicefilter.hpp @@ -0,0 +1,94 @@ +#ifndef INCLUDE_SERVICEFILTER_HPP +#define INCLUDE_SERVICEFILTER_HPP + +#include "util.hpp" +#include +#include + +class CServiceFilter +{ +public: + CServiceFilter(); + void SetProgramNumberOrIndex(int n) { m_programNumberOrIndex = n; } + void SetAudio1Mode(int mode); + void SetAudio2Mode(int mode); + void SetCaptionMode(int mode); + void SetSuperimposeMode(int mode); + void AddPacket(const uint8_t *packet); + const std::vector &GetPackets() const { return m_packets; } + void ClearPackets() { m_packets.clear(); } + +private: + const uint8_t H_262_VIDEO = 0x02; + const uint8_t MPEG2_AUDIO = 0x04; + const uint8_t PES_PRIVATE_DATA = 0x06; + const uint8_t ADTS_TRANSPORT = 0x0f; + const uint8_t AVC_VIDEO = 0x1b; + const uint8_t H_265_VIDEO = 0x24; + + static std::vector::const_iterator FindNitRef(const std::vector &pmt); + std::vector::const_iterator FindTargetPmtRef(const std::vector &pmt) const; + void AddPat(int transportStreamID, int programNumber, bool addNit); + void AddPmt(const PSI &psi); + void AddPcrAdaptation(const uint8_t *pcr); + void ChangePidAndAddPacket(const uint8_t *packet, int pid, uint8_t counter = 0xff); + void AddAudioPesPackets(uint8_t index, int64_t targetPts, int64_t &pts, uint8_t &counter); + void Add64MsecAudioPesPacket(uint8_t index, int64_t pts, uint8_t &counter); + static int64_t GetAudioPresentationTimeStamp(int unitStart, const uint8_t *payload, int payloadSize); + static bool AccumulatePesPackets(std::vector &unitPackets, const uint8_t *packet, int unitStart); + static void ConcatenatePayload(std::vector &dest, const std::vector &unitPackets, bool &pcrFlag, uint8_t (&pcr)[6]); + void AddCaptionManagementPesPacket(int64_t pts, uint8_t counter); + void AddSuperimposeManagementPesPacket(uint8_t counter); + void AddAudioPesPackets(const std::vector &pes, int pid, uint8_t &counter, int64_t &ptsPcrDiff, const uint8_t *pcr); + bool TransmuxMonoToStereo(const std::vector &unitPackets, std::vector &workspace, + int pid, uint8_t &counter, int64_t &ptsPcrDiff); + bool TransmuxDualMono(const std::vector &unitPackets); + + int m_programNumberOrIndex; + int m_audio1Mode; + int m_audio2Mode; + bool m_audio1MuxToStereo; + bool m_audio2MuxToStereo; + bool m_audio1MuxDualMono; + int m_captionMode; + int m_superimposeMode; + bool m_captionInsertManagementPacket; + bool m_superimposeInsertManagementPacket; + std::vector m_packets; + PAT m_pat; + PSI m_pmtPsi; + int m_videoPid; + int m_audio1Pid; + int m_audio2Pid; + uint8_t m_audio1StreamType; + uint8_t m_audio2StreamType; + int m_captionPid; + int m_superimposePid; + int m_pcrPid; + int64_t m_pcr; + uint8_t m_patCounter; + uint8_t m_pmtCounter; + uint8_t m_audio1PesCounter; + uint8_t m_audio2PesCounter; + uint8_t m_captionPesCounter; + uint8_t m_superimposePesCounter; + bool m_isAudio1DualMono; + std::vector m_audio1UnitPackets; + std::vector m_audio2UnitPackets; + std::vector m_audio1MuxWorkspace; + std::vector m_audio2MuxWorkspace; + std::vector m_audio1MuxDualMonoWorkspace; + int64_t m_audio1Pts; + int64_t m_audio2Pts; + int64_t m_audio1PtsPcrDiff; + int64_t m_audio2PtsPcrDiff; + int64_t m_captionManagementPcr; + int64_t m_superimposeManagementPcr; + std::vector m_buf; + std::vector m_destLeftBuf; + std::vector m_destRightBuf; + std::vector m_lastPat; + std::vector m_lastPmt; +}; + +#endif diff --git a/tsreadex/traceb24.cpp b/tsreadex/traceb24.cpp new file mode 100644 index 0000000..bb11bac --- /dev/null +++ b/tsreadex/traceb24.cpp @@ -0,0 +1,1090 @@ +#include "traceb24.hpp" +#include + +CTraceB24Caption::CTraceB24Caption() + : m_fp(nullptr) + , m_firstPmtPid(0) + , m_captionPid(0) + , m_superimposePid(0) + , m_pcrPid(0) + , m_pcr(-1) +{ + static const PAT zeroPat = {}; + m_pat = zeroPat; + m_firstPmtPsi = zeroPat.psi; +} + +void CTraceB24Caption::AddPacket(const uint8_t *packet) +{ + if (!m_fp) { + return; + } + + int unitStart = extract_ts_header_unit_start(packet); + int pid = extract_ts_header_pid(packet); + int adaptation = extract_ts_header_adaptation(packet); + int counter = extract_ts_header_counter(packet); + int payloadSize = get_ts_payload_size(packet); + const uint8_t *payload = packet + 188 - payloadSize; + + if (pid == 0) { + extract_pat(&m_pat, payload, payloadSize, unitStart, counter); + auto itFirstPmt = std::find_if(m_pat.pmt.begin(), m_pat.pmt.end(), [](const PMT_REF &pmt) { return pmt.program_number != 0; }); + if (m_firstPmtPid != 0 && (itFirstPmt == m_pat.pmt.end() || itFirstPmt->pmt_pid != m_firstPmtPid)) { + m_firstPmtPid = 0; + static const PSI zeroPsi = {}; + m_firstPmtPsi = zeroPsi; + } + if (itFirstPmt != m_pat.pmt.end()) { + m_firstPmtPid = itFirstPmt->pmt_pid; + } + } + else if (pid == m_firstPmtPid) { + int done; + do { + done = extract_psi(&m_firstPmtPsi, payload, payloadSize, unitStart, counter); + if (m_firstPmtPsi.version_number && m_firstPmtPsi.table_id == 2) { + CheckPmt(m_firstPmtPsi); + } + } + while (!done); + } + else if (pid == m_pcrPid) { + if (adaptation & 2) { + int adaptationLength = packet[4]; + if (adaptationLength >= 6 && !!(packet[5] & 0x10)) { + bool firstPcr = m_pcr < 0; + m_pcr = (packet[10] >> 7) | + (packet[9] << 1) | + (packet[8] << 9) | + (packet[7] << 17) | + (static_cast(packet[6]) << 25); + if (firstPcr) { + fprintf(m_fp, "pcrpid=0x%04X;pcr=%010lld\n", m_pcrPid, static_cast(m_pcr)); + fflush(m_fp); + } + } + } + } + else if (pid == m_captionPid || pid == m_superimposePid) { + auto &pesPair = pid == m_captionPid ? m_captionPes : m_superimposePes; + int &pesCounter = pesPair.first; + std::vector &pes = pesPair.second; + if (unitStart) { + pesCounter = counter; + pes.assign(payload, payload + payloadSize); + } + else if (!pes.empty()) { + pesCounter = (pesCounter + 1) & 0x0f; + if (pesCounter == counter) { + pes.insert(pes.end(), payload, payload + payloadSize); + } + else { + // Ignore packets until the next unit-start + pes.clear(); + } + } + if (pes.size() >= 6) { + size_t pesPacketLength = (pes[4] << 8) | pes[5]; + if (pes.size() >= 6 + pesPacketLength) { + // PES has been accumulated + pes.resize(6 + pesPacketLength); + OutputPrivateDataPes(pes, pid == m_captionPid ? m_captionDrcsList : m_superimposeDrcsList, + pid == m_captionPid ? m_captionLangTags : m_superimposeLangTags); + pes.clear(); + } + } + } +} + +void CTraceB24Caption::CheckPmt(const PSI &psi) +{ + const uint8_t PES_PRIVATE_DATA = 0x06; + + if (psi.section_length < 9) { + return; + } + const uint8_t *table = psi.data; + m_pcrPid = ((table[8] & 0x1f) << 8) | table[9]; + if (m_pcrPid == 0x1fff) { + m_pcr = -1; + } + int programInfoLength = ((table[10] & 0x03) << 8) | table[11]; + int pos = 3 + 9 + programInfoLength; + if (psi.section_length < pos) { + return; + } + + int captionPid = 0; + int superimposePid = 0; + int tableLen = 3 + psi.section_length - 4/*CRC32*/; + while (pos + 4 < tableLen) { + int streamType = table[pos]; + int esPid = ((table[pos + 1] & 0x1f) << 8) | table[pos + 2]; + int esInfoLength = ((table[pos + 3] & 0x03) << 8) | table[pos + 4]; + if (pos + 5 + esInfoLength <= tableLen) { + int componentTag = 0xff; + for (int i = pos + 5; i + 2 < pos + 5 + esInfoLength; i += 2 + table[i + 1]) { + // stream_identifier_descriptor + if (table[i] == 0x52) { + componentTag = table[i + 2]; + break; + } + } + // ARIB caption/superimpose ("A-Profile" only) + if (streamType == PES_PRIVATE_DATA && + (componentTag == 0x30 || componentTag == 0x38)) { + if (componentTag == 0x30) { + captionPid = esPid; + } + else { + superimposePid = esPid; + } + } + } + pos += 5 + esInfoLength; + } + + if (m_captionPid != captionPid) { + m_captionPid = captionPid; + m_captionPes.second.clear(); + m_captionDrcsList.clear(); + std::fill_n(m_captionLangTags, 8, LANG_TAG_ABSENT); + } + if (m_superimposePid != superimposePid) { + m_superimposePid = superimposePid; + m_superimposePes.second.clear(); + m_superimposeDrcsList.clear(); + std::fill_n(m_superimposeLangTags, 8, LANG_TAG_ABSENT); + } +} + +void CTraceB24Caption::OutputPrivateDataPes(const std::vector &pes, + std::vector &drcsList, LANG_TAG_TYPE (&langTags)[8]) +{ + const uint8_t PRIVATE_STREAM_1 = 0xbd; + const uint8_t PRIVATE_STREAM_2 = 0xbf; + + size_t payloadPos = 0; + int64_t pts = -1; + if (pes[0] == 0 && pes[1] == 0 && pes[2] == 1) { + int streamID = pes[3]; + if (streamID == PRIVATE_STREAM_1 && pes.size() >= 9) { + int ptsDtsFlags = pes[7] >> 6; + payloadPos = 9 + pes[8]; + if (ptsDtsFlags >= 2 && pes.size() >= 14) { + pts = (pes[13] >> 1) | + (pes[12] << 7) | + ((pes[11] & 0xfe) << 14) | + (pes[10] << 22) | + (static_cast(pes[9] & 0x0e) << 29); + } + } + else if (streamID == PRIVATE_STREAM_2) { + payloadPos = 6; + if (m_pcr >= 0) { + pts = m_pcr; + } + } + } + if (payloadPos == 0 || payloadPos + 1 >= pes.size() || pts < 0) { + return; + } + int dataIdentifier = pes[payloadPos]; + int privateStreamID = pes[payloadPos + 1]; + if ((dataIdentifier != 0x80 && dataIdentifier != 0x81) || + privateStreamID != 0xff) { + // Not an ARIB Synchronized/Asynchronous PES data + return; + } + + PARSE_PRIVATE_DATA_RESULT ret = ParsePrivateData(m_buf, m_intBuf, pes.data() + payloadPos, pes.size() - payloadPos, drcsList, langTags); + if (ret != PARSE_PRIVATE_DATA_FAILED_NEED_MANAGEMENT) { + int64_t ptsPcrDiff = (0x200000000 + pts - m_pcr) & 0x1ffffffff; + if (ptsPcrDiff >= 0x100000000) { + ptsPcrDiff -= 0x200000000; + } + fprintf(m_fp, "pts=%010lld;pcrrel=%+08d", + static_cast(pts), + static_cast(m_pcr < 0 ? -9999999 : std::min(std::max(ptsPcrDiff, -9999999), 9999999))); + if (ret == PARSE_PRIVATE_DATA_SUCCEEDED) { + for (size_t i = 0; i + 1 < m_intBuf.size(); ++i) { + fprintf(m_fp, "%s%d", i == 0 ? ";text=" : ",", m_intBuf[i + 1] - m_intBuf[i]); + } + } + fprintf(m_fp, ";b24%s", dataIdentifier == 0x81 ? "superimpose" : "caption"); + if (ret == PARSE_PRIVATE_DATA_SUCCEEDED) { + m_buf.push_back('\n'); + fwrite(m_buf.data(), 1, m_buf.size(), m_fp); + } + else { + fprintf(m_fp, "err=%s\n", + ret == PARSE_PRIVATE_DATA_FAILED_CRC ? "crc" : + ret == PARSE_PRIVATE_DATA_FAILED_UNSUPPORTED ? "unsupported" : "unknown"); + } + fflush(m_fp); + } +} + +namespace +{ +enum GS_CLASS +{ + GS_1BYTE_G, + GS_2BYTE_G, + GS_1BYTE_DRCS, + GS_2BYTE_DRCS, +}; + +const uint8_t GS_HIRAGANA = 0x30; +const uint8_t GS_KATAKANA = 0x31; +const uint8_t GS_PROP_ASCII = 0x36; +const uint8_t GS_PROP_HIRAGANA = 0x37; +const uint8_t GS_PROP_KATAKANA = 0x38; +const uint8_t GS_JIS_KANJI1 = 0x39; +const uint8_t GS_JIS_KANJI2 = 0x3a; +const uint8_t GS_ADDITIONAL_SYMBOLS = 0x3b; +const uint8_t GS_KANJI = 0x42; +const uint8_t GS_JISX_KATAKANA = 0x49; +const uint8_t GS_ASCII = 0x4a; +const uint8_t GS_LATIN_EXTENSION = 0x4b; +const uint8_t GS_LATIN_SPECIAL = 0x4c; +const uint8_t GS_DRCS_0 = 0x40; +const uint8_t GS_DRCS_1 = 0x41; +const uint8_t GS_DRCS_15 = 0x4f; +const uint8_t GS_MACRO = 0x70; + +extern const char16_t FullwidthAsciiTable[94]; +extern const char16_t HiraganaTable[94]; +extern const char16_t KatakanaTable[94]; +extern const char16_t JisXKatakanaTable[94]; +extern const char16_t LatinExtensionTable[94]; +extern const char16_t LatinSpecialTable[94]; +extern const char16_t JisTable[84 * 94 + 1]; +extern const char32_t GaijiTable[7 * 94 + 1]; +extern const uint8_t DefaultMacro[16][20]; + +void AddEscapedChar(std::vector &buf, uint8_t c) +{ + buf.push_back('%'); + buf.push_back(((c >> 4) > 9 ? '7' : '0') + (c >> 4)); + buf.push_back(((c & 15) > 9 ? '7' : '0') + (c & 15)); +} + +void AddChar(std::vector &buf, uint8_t c) +{ + // control characters or % + if (c < 0x20 || c == 0x25 || c == 0x7f) { + AddEscapedChar(buf, c); + } + else { + buf.push_back(c); + } +} + +void AddChar32(std::vector &buf, char32_t x) +{ + if (x < 0x80) { + AddChar(buf, static_cast(x)); + } + else if (x < 0x800) { + AddChar(buf, static_cast(0xc0 | (x >> 6))); + AddChar(buf, static_cast(0x80 | (x & 0x3f))); + } + else if (x < 0x10000) { + AddChar(buf, static_cast(0xe0 | (x >> 12))); + AddChar(buf, static_cast(0x80 | ((x >> 6) & 0x3f))); + AddChar(buf, static_cast(0x80 | (x & 0x3f))); + } + else { + AddChar(buf, static_cast(0xf0 | (x >> 18))); + AddChar(buf, static_cast(0x80 | ((x >> 12) & 0x3f))); + AddChar(buf, static_cast(0x80 | ((x >> 6) & 0x3f))); + AddChar(buf, static_cast(0x80 | (x & 0x3f))); + } +} + +void AddAscii(std::vector &buf, uint8_t c) +{ + if (c < 0x80) { + AddChar(buf, c); + } + else { + AddChar32(buf, U'\uFFFD'); + } +} + +uint8_t ReadByte(const uint8_t *&data, const uint8_t *dataEnd) +{ + if (data == dataEnd) { + return 0; + } + return *(data++); +} + +void InitializeArib8(std::pair (&gbuf)[4], int &gl, int &gr, bool isLatin) +{ + if (isLatin) { + gbuf[0] = std::make_pair(GS_1BYTE_G, GS_ASCII); + gbuf[2] = std::make_pair(GS_1BYTE_G, GS_LATIN_EXTENSION); + gbuf[3] = std::make_pair(GS_1BYTE_G, GS_LATIN_SPECIAL); + } + else { + gbuf[0] = std::make_pair(GS_2BYTE_G, GS_KANJI); + gbuf[2] = std::make_pair(GS_1BYTE_G, GS_HIRAGANA); + gbuf[3] = std::make_pair(GS_1BYTE_DRCS, GS_MACRO); + } + gbuf[1] = std::make_pair(GS_1BYTE_G, GS_ASCII); + gl = 0; + gr = 2; +} + +void CheckReadableTextPosList(std::vector &textPosList, const std::vector &buf, bool isNextReadable) +{ + if ((textPosList.size() % 2 != 0) == isNextReadable) { + int codeCount = 0; + for (size_t i = 0; i < buf.size(); ++i) { + if ((buf[i] & 0xc0) != 0x80) { + ++codeCount; + } + } + textPosList.push_back(codeCount); + } +} + +void AnalizeArib8(std::vector &buf, std::vector &textPosList, const uint8_t *&data, const uint8_t *dataEnd, + const std::vector &drcsList, std::pair (&gbuf)[4], int &gl, int &gr, bool isLatin) +{ + std::pair *gss = nullptr; + while (data != dataEnd) { + uint8_t b = ReadByte(data, dataEnd); + if (b <= 0x20) { + // C0 + gss = nullptr; + if (b == 0x0e) { + // LS1 + gl = 1; + } + else if (b == 0x0f) { + // LS0 + gl = 0; + } + else if (b == 0x19) { + // SS2 + gss = &gbuf[2]; + } + else if (b == 0x1b) { + // ESC + b = ReadByte(data, dataEnd); + if (b == 0x24) { + b = ReadByte(data, dataEnd); + if (0x28 <= b && b <= 0x2b) { + uint8_t c = ReadByte(data, dataEnd); + if (c == 0x20) { + gbuf[b - 0x28] = std::make_pair(GS_2BYTE_DRCS, ReadByte(data, dataEnd)); + } + else if (0x29 <= b && b <= 0x2b) { + gbuf[b - 0x28] = std::make_pair(GS_2BYTE_G, c); + } + } + else { + gbuf[0] = std::make_pair(GS_2BYTE_G, b); + } + } + else if (0x28 <= b && b <= 0x2b) { + uint8_t c = ReadByte(data, dataEnd); + if (c == 0x20) { + gbuf[b - 0x28] = std::make_pair(GS_1BYTE_DRCS, ReadByte(data, dataEnd)); + } + else { + gbuf[b - 0x28] = std::make_pair(GS_1BYTE_G, c); + } + } + else if (b == 0x6e) { + gl = 2; + } + else if (b == 0x6f) { + gl = 3; + } + else if (b == 0x7c) { + gr = 3; + } + else if (b == 0x7d) { + gr = 2; + } + else if (b == 0x7e) { + gr = 1; + } + } + else if (b == 0x1d) { + // SS3 + gss = &gbuf[3]; + } + else if (b != 0) { + CheckReadableTextPosList(textPosList, buf, b == 0x20); + AddChar(buf, b); + if (b == 0x0c) { + // CS + InitializeArib8(gbuf, gl, gr, isLatin); + } + else if (b == 0x16) { + // PAPF + AddAscii(buf, ReadByte(data, dataEnd)); + } + else if (b == 0x1c) { + // APS + AddAscii(buf, ReadByte(data, dataEnd)); + AddAscii(buf, ReadByte(data, dataEnd)); + } + } + } + else if (0x7f <= b && b <= 0xa0) { + // C1 + gss = nullptr; + if (b == 0x95) { + // MACRO (unsupported) + b = ReadByte(data, dataEnd); + while (data != dataEnd) { + uint8_t c = ReadByte(data, dataEnd); + if (b == 0x95 && c == 0x4f) { + break; + } + b = c; + } + } + else { + if (b == 0x7f) { + CheckReadableTextPosList(textPosList, buf, false); + AddChar(buf, b); + } + else if (b == 0xa0) { + CheckReadableTextPosList(textPosList, buf, true); + AddChar32(buf, U'\u00A0'); + } + else { + // caret notation + CheckReadableTextPosList(textPosList, buf, false); + buf.push_back('%'); + AddChar(buf, '^'); + AddChar(buf, b - 0x40); + } + if (b == 0x8b || b == 0x91 || b == 0x93 || b == 0x94 || b == 0x97 || b == 0x98) { + // SZX, FLC, POL, WMM, HLC, RPC + AddAscii(buf, ReadByte(data, dataEnd)); + } + else if (b == 0x90) { + // COL + b = ReadByte(data, dataEnd); + AddAscii(buf, b); + if (b == 0x20) { + AddAscii(buf, ReadByte(data, dataEnd)); + } + } + else if (b == 0x9d) { + // TIME + b = ReadByte(data, dataEnd); + AddAscii(buf, b); + if (b == 0x20) { + AddAscii(buf, ReadByte(data, dataEnd)); + } + else { + while (data != dataEnd && (b < 0x40 || 0x43 < b)) { + b = ReadByte(data, dataEnd); + AddAscii(buf, b); + } + } + } + else if (b == 0x9b) { + // CSI + while (data != dataEnd && b != 0x20) { + b = ReadByte(data, dataEnd); + AddAscii(buf, b); + } + b = ReadByte(data, dataEnd); + AddAscii(buf, b); + if (b == 0x53) { + // SWF + InitializeArib8(gbuf, gl, gr, isLatin); + } + } + } + } + else if (b < 0xff) { + // GL, GR + std::pair g = gss ? *gss : b < 0x7f ? gbuf[gl] : gbuf[gr]; + gss = nullptr; + b &= 0x7f; + if (g.first == GS_1BYTE_G) { + CheckReadableTextPosList(textPosList, buf, true); + if (g.second == GS_ASCII || g.second == GS_PROP_ASCII) { + if (isLatin) { + AddChar(buf, b); + } + else { + AddChar32(buf, static_cast(FullwidthAsciiTable[b - 0x21])); + } + } + else { + char16_t x = g.second == GS_HIRAGANA || g.second == GS_PROP_HIRAGANA ? HiraganaTable[b - 0x21] : + g.second == GS_KATAKANA || g.second == GS_PROP_KATAKANA ? KatakanaTable[b - 0x21] : + g.second == GS_JISX_KATAKANA ? JisXKatakanaTable[b - 0x21] : + g.second == GS_LATIN_EXTENSION ? LatinExtensionTable[b - 0x21] : + g.second == GS_LATIN_SPECIAL ? LatinSpecialTable[b - 0x21] : u'\uFFFD'; + AddChar32(buf, static_cast(x)); + } + } + else if (g.first == GS_2BYTE_G) { + CheckReadableTextPosList(textPosList, buf, true); + uint8_t c = ReadByte(data, dataEnd) & 0x7f; + if (g.second == GS_JIS_KANJI1 || + g.second == GS_JIS_KANJI2 || + g.second == GS_ADDITIONAL_SYMBOLS || + g.second == GS_KANJI) { + if (b < 0x21 + 84 && 0x21 <= c && c < 0x21 + 94) { + AddChar32(buf, static_cast(JisTable[(b - 0x21) * 94 + (c - 0x21)])); + } + else { + int x = (b << 8) | c; + AddChar32(buf, 0x7521 <= x && x < 0x7521 + 94 ? GaijiTable[5 * 94 + x - 0x7521] : + 0x7621 <= x && x < 0x7621 + 94 ? GaijiTable[6 * 94 + x - 0x7621] : + 0x7a21 <= x && x < 0x7a21 + 94 ? GaijiTable[x - 0x7a21] : + 0x7b21 <= x && x < 0x7b21 + 94 ? GaijiTable[94 + x - 0x7b21] : + 0x7c21 <= x && x < 0x7c21 + 94 ? GaijiTable[2 * 94 + x - 0x7c21] : + 0x7d21 <= x && x < 0x7d21 + 94 ? GaijiTable[3 * 94 + x - 0x7d21] : + 0x7e21 <= x && x < 0x7e21 + 94 ? GaijiTable[4 * 94 + x - 0x7e21] : U'\uFFFD'); + } + } + else { + AddChar32(buf, U'\uFFFD'); + } + } + else if (g.first == GS_1BYTE_DRCS) { + if (GS_DRCS_1 <= g.second && g.second <= GS_DRCS_15) { + // 1-byte DRCS mapping + uint16_t charCode = ((g.second - GS_DRCS_0) << 8) | b; + char32_t x = U'\uFFFD'; + if (!drcsList.empty()) { + auto it = std::find(drcsList.begin() + 1, drcsList.end(), charCode); + if (it != drcsList.end()) { + x = static_cast(it - 1 - drcsList.begin() + 0xec00); + } + } + CheckReadableTextPosList(textPosList, buf, true); + AddChar32(buf, x); + } + else if (g.second == GS_MACRO) { + if (0x60 <= b && b <= 0x6f) { + const uint8_t *macro = DefaultMacro[b & 0x0f]; + AnalizeArib8(buf, textPosList, macro, macro + sizeof(DefaultMacro[0]), drcsList, gbuf, gl, gr, isLatin); + } + else { + AddChar32(buf, U'\uFFFD'); + } + } + else { + AddChar32(buf, U'\uFFFD'); + } + } + else if (g.first == GS_2BYTE_DRCS) { + uint8_t c = ReadByte(data, dataEnd) & 0x7f; + if (g.second == GS_DRCS_0) { + // 2-byte DRCS mapping + uint16_t charCode = (b << 8) | c; + char32_t x = U'\uFFFD'; + if (!drcsList.empty()) { + auto it = std::find(drcsList.begin() + 1, drcsList.end(), charCode); + if (it != drcsList.end()) { + x = static_cast(it - 1 - drcsList.begin() + 0xec00); + } + } + CheckReadableTextPosList(textPosList, buf, true); + AddChar32(buf, x); + } + else { + AddChar32(buf, U'\uFFFD'); + } + } + } + else { + gss = nullptr; + } + } +} + +void AddArib8AsUtf8(std::vector &buf, std::vector &textPosList, const uint8_t *data, size_t dataSize, + const std::vector &drcsList, bool isLatin) +{ + std::pair gbuf[4]; + int gl, gr; + InitializeArib8(gbuf, gl, gr, isLatin); + AnalizeArib8(buf, textPosList, data, data + dataSize, drcsList, gbuf, gl, gr, isLatin); + CheckReadableTextPosList(textPosList, buf, false); +} + +size_t AddEscapedData(std::vector &buf, const uint8_t *data, size_t dataSize) +{ + for (size_t i = 0; i < dataSize; ++i) { + AddEscapedChar(buf, data[i]); + } + return dataSize; +} + +size_t AddBase64EncodedData(std::vector &buf, const uint8_t *data, size_t dataSize) +{ + static const char code[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + for (size_t i = 0; i < dataSize; i += 3) { + buf.push_back(code[data[i] >> 2]); + buf.push_back(code[((data[i] & 0x3) << 4) | (i + 1 < dataSize ? data[i + 1] >> 4 : 0)]); + buf.push_back(code[i + 1 < dataSize ? ((data[i + 1] & 0xf) << 2) | (i + 2 < dataSize ? data[i + 2] >> 6 : 0) : 64]); + buf.push_back(code[i + 2 < dataSize ? data[i + 2] & 0x3f : 64]); + } + return dataSize; +} + +void AddUcs(std::vector &buf, const uint8_t *data, size_t dataSize) +{ + if (dataSize > 0 && (data[0] == 0xfe || data[0] == 0xff)) { + AddEscapedData(buf, data, dataSize); + } + else { + // UTF-8 + for (size_t i = 0; i < dataSize; ++i) { + if (data[i] == 0xc2 && i + 1 < dataSize && 0x80 <= data[i + 1] && data[i + 1] <= 0x9f) { + // C1, caret notation + buf.push_back('%'); + AddChar(buf, '^'); + AddChar(buf, data[++i] - 0x40); + } + else { + AddChar(buf, data[i]); + } + } + } +} +} + +CTraceB24Caption::PARSE_PRIVATE_DATA_RESULT +CTraceB24Caption::ParsePrivateData(std::vector &buf, std::vector &textPosList, const uint8_t *data, size_t dataSize, + std::vector &drcsList, LANG_TAG_TYPE (&langTags)[8]) +{ + const uint8_t BEGIN_UNIT_BRACE[] = {'%', '=', '{'}; + const uint8_t END_UNIT_BRACE[] = {'%', '=', '}'}; + const uint8_t BEGIN_BASE64_BRACE[] = {'%', '+', '{'}; + const uint8_t END_BASE64_BRACE[] = {'%', '+', '}'}; + + if (dataSize < 3) { + return PARSE_PRIVATE_DATA_FAILED; + } + size_t pos = 3 + (data[2] & 0x0f); + if (pos + 4 >= dataSize) { + return PARSE_PRIVATE_DATA_FAILED; + } + + // data_group() + uint8_t dgiType = (data[pos] >> 2) & 0x1f; + size_t dataGroupEnd = pos + 5 + ((data[pos + 3] << 8) | data[pos + 4]); + if (dgiType > 8) { + return PARSE_PRIVATE_DATA_FAILED_UNSUPPORTED; + } + if (dgiType != 0 && langTags[dgiType - 1] == LANG_TAG_ABSENT) { + return PARSE_PRIVATE_DATA_FAILED_NEED_MANAGEMENT; + } + if (dataGroupEnd + 2 > dataSize) { + return PARSE_PRIVATE_DATA_FAILED; + } + if (calc_crc16_ccitt(data + pos, static_cast(dataGroupEnd + 2 - pos)) != 0) { + return PARSE_PRIVATE_DATA_FAILED_CRC; + } + buf.clear(); + buf.push_back('0' + dgiType); + buf.push_back('='); + textPosList.clear(); + CheckReadableTextPosList(textPosList, buf, false); + pos += AddEscapedData(buf, data + pos, 3); + // omit data_group_size + pos += 2; + if (pos + 3 >= dataGroupEnd) { + return PARSE_PRIVATE_DATA_FAILED; + } + + // caption_management_data() or caption_data() + int tmd = data[pos] >> 6; + AddEscapedChar(buf, data[pos++]); + if ((dgiType != 0 && tmd == 1) || tmd == 2) { + if (pos + 7 >= dataGroupEnd) { + return PARSE_PRIVATE_DATA_FAILED; + } + pos += AddEscapedData(buf, data + pos, 5); + } + + LANG_TAG_TYPE lang = LANG_TAG_UNKNOWN; + if (dgiType == 0) { + // caption_management_data() + std::fill_n(langTags, 8, LANG_TAG_ABSENT); + int numLanguages = data[pos]; + AddEscapedChar(buf, data[pos++]); + + for (int i = 0; i < numLanguages && pos + 7 < dataGroupEnd; ++i) { + int tag = data[pos] >> 5; + int dmf = data[pos] & 0x0f; + AddEscapedChar(buf, data[pos++]); + if (12 <= dmf && dmf <= 14) { + if (pos + 7 >= dataGroupEnd) { + return PARSE_PRIVATE_DATA_FAILED; + } + AddEscapedChar(buf, data[pos++]); + } + int tcs = (data[pos + 3] >> 2) & 0x03; + lang = tcs == 1 ? LANG_TAG_UCS : + tcs != 0 ? LANG_TAG_UNKNOWN : + (data[pos] == 'p' && data[pos + 1] == 'o' && data[pos + 2] == 'r') ? LANG_TAG_ARIB8_LATIN : + (data[pos] == 's' && data[pos + 1] == 'p' && data[pos + 2] == 'a') ? LANG_TAG_ARIB8_LATIN : LANG_TAG_ARIB8; + langTags[tag] = lang; + for (int j = 0; j < 3; ++j) { + if (data[pos] < 0x80) { + AddChar(buf, data[pos++]); + } + else { + AddEscapedChar(buf, data[pos++]); + } + } + // tcs 0->1 + AddEscapedChar(buf, data[pos++] | (lang == LANG_TAG_ARIB8 || lang == LANG_TAG_ARIB8_LATIN ? 0x04 : 0)); + } + } + else { + // caption_data() + lang = langTags[dgiType - 1]; + } + size_t dataUnitLoopEnd = pos + 3 + ((data[pos] << 16) | (data[pos + 1] << 8) | data[pos + 2]); + if (dataUnitLoopEnd > dataGroupEnd) { + return PARSE_PRIVATE_DATA_FAILED; + } + pos += 3; + + // Repleace data_unit_loop_length with "%={" + buf.insert(buf.end(), BEGIN_UNIT_BRACE, BEGIN_UNIT_BRACE + sizeof(BEGIN_UNIT_BRACE)); + + while (pos + 4 < dataUnitLoopEnd) { + // data_unit() + AddEscapedChar(buf, data[pos++]); + int unitParameter = data[pos]; + if (unitParameter == 0x30 && (lang == LANG_TAG_ARIB8 || lang == LANG_TAG_ARIB8_LATIN)) { + // "shall be the DRCS-0 set" (STD-B24) + AddEscapedChar(buf, 0x31); + ++pos; + } + else { + AddEscapedChar(buf, data[pos++]); + } + size_t dataUnitSize = (data[pos] << 16) | (data[pos + 1] << 8) | data[pos + 2]; + pos += 3; + if (pos + dataUnitSize > dataUnitLoopEnd) { + return PARSE_PRIVATE_DATA_FAILED; + } + // Repleace data_unit_size with "%={" + buf.insert(buf.end(), BEGIN_UNIT_BRACE, BEGIN_UNIT_BRACE + sizeof(BEGIN_UNIT_BRACE)); + + if (unitParameter == 0x20) { + // Statement body + if (lang == LANG_TAG_ARIB8 || lang == LANG_TAG_ARIB8_LATIN) { + AddArib8AsUtf8(buf, textPosList, data + pos, dataUnitSize, drcsList, lang == LANG_TAG_ARIB8_LATIN); + pos += dataUnitSize; + } + else if (lang == LANG_TAG_UCS) { + AddUcs(buf, data + pos, dataUnitSize); + pos += dataUnitSize; + } + else { + pos += AddEscapedData(buf, data + pos, dataUnitSize); + } + } + else if (unitParameter == 0x30 || unitParameter == 0x31) { + // Drcs_data_structure() + size_t drcsDataEnd = pos + dataUnitSize; + if (pos < drcsDataEnd) { + int numberOfCode = data[pos]; + AddEscapedChar(buf, data[pos++]); + for (int i = 0; i < numberOfCode && pos + 2 < drcsDataEnd; ++i) { + uint16_t charCode = unitParameter == 0x31 ? (data[pos] << 8) | data[pos + 1] : + ((data[pos] - GS_DRCS_0) << 8) | data[pos + 1]; + if (drcsList.empty()) { + // drcsList[0] points to the last mapping index. + drcsList.push_back(0); + } + auto it = std::find(drcsList.begin() + 1, drcsList.end(), charCode); + if (it == drcsList.end()) { + // mapping + drcsList[0] = drcsList[0] % 1024 + 1; + if (drcsList[0] >= drcsList.size()) { + drcsList.push_back(0); + } + it = drcsList.begin() + drcsList[0]; + *it = charCode; + } + // U+EC00 - U+EFFF (1024 characters) + AddEscapedChar(buf, static_cast(0xec + ((it - 1 - drcsList.begin()) >> 8))); + AddEscapedChar(buf, static_cast(it - 1 - drcsList.begin())); + pos += 2; + + int numberOfFont = data[pos]; + AddEscapedChar(buf, data[pos++]); + for (int j = 0; j < numberOfFont && pos < drcsDataEnd; ++j) { + int mode = data[pos] & 0x0f; + AddEscapedChar(buf, data[pos++]); + size_t drcsHead = drcsDataEnd - pos; + size_t drcsBody = 0; + if (mode <= 1) { + if (drcsHead >= 3) { + uint8_t depth = data[pos]; + drcsBody = ((depth == 0 ? 1 : depth <= 2 ? 2 : depth <= 6 ? 3 : + depth <= 14 ? 4 : depth <= 30 ? 5 : depth <= 62 ? 6 : + depth <= 126 ? 7 : depth <= 254 ? 8 : 9) * data[pos + 1] * data[pos + 2] + 7) / 8; + drcsBody = std::min(drcsBody, drcsHead - 3); + drcsHead = 3; + } + } + else { + if (drcsHead >= 4) { + drcsBody = (data[pos + 2] << 8) | data[pos + 3]; + drcsBody = std::min(drcsBody, drcsHead - 4); + drcsHead = 4; + } + } + pos += AddEscapedData(buf, data + pos, drcsHead); + if (drcsBody > 0) { + buf.insert(buf.end(), BEGIN_BASE64_BRACE, BEGIN_BASE64_BRACE + sizeof(BEGIN_BASE64_BRACE)); + pos += AddBase64EncodedData(buf, data + pos, drcsBody); + buf.insert(buf.end(), END_BASE64_BRACE, END_BASE64_BRACE + sizeof(END_BASE64_BRACE)); + } + } + } + } + } + else { + pos += AddEscapedData(buf, data + pos, dataUnitSize); + } + buf.insert(buf.end(), END_UNIT_BRACE, END_UNIT_BRACE + sizeof(END_UNIT_BRACE)); + } + buf.insert(buf.end(), END_UNIT_BRACE, END_UNIT_BRACE + sizeof(END_UNIT_BRACE)); + + // omit CRC_16 + return PARSE_PRIVATE_DATA_SUCCEEDED; +} + +namespace +{ +const char16_t FullwidthAsciiTable[94] = +{ + u'!', u'"', u'#', u'$', u'%', u'&', u''', + u'(', u')', u'*', u'+', u',', u'-', u'.', u'/', + u'0', u'1', u'2', u'3', u'4', u'5', u'6', u'7', + u'8', u'9', u':', u';', u'<', u'=', u'>', u'?', + u'@', u'A', u'B', u'C', u'D', u'E', u'F', u'G', + u'H', u'I', u'J', u'K', u'L', u'M', u'N', u'O', + u'P', u'Q', u'R', u'S', u'T', u'U', u'V', u'W', + u'X', u'Y', u'Z', u'[', u'¥', u']', u'^', u'_', + u'`', u'a', u'b', u'c', u'd', u'e', u'f', u'g', + u'h', u'i', u'j', u'k', u'l', u'm', u'n', u'o', + u'p', u'q', u'r', u's', u't', u'u', u'v', u'w', + u'x', u'y', u'z', u'{', u'|', u'}', u' ̄' +}; + +const char16_t HiraganaTable[94] = +{ + u'ぁ', u'あ', u'ぃ', u'い', u'ぅ', u'う', u'ぇ', + u'え', u'ぉ', u'お', u'か', u'が', u'き', u'ぎ', u'く', + u'ぐ', u'け', u'げ', u'こ', u'ご', u'さ', u'ざ', u'し', + u'じ', u'す', u'ず', u'せ', u'ぜ', u'そ', u'ぞ', u'た', + u'だ', u'ち', u'ぢ', u'っ', u'つ', u'づ', u'て', u'で', + u'と', u'ど', u'な', u'に', u'ぬ', u'ね', u'の', u'は', + u'ば', u'ぱ', u'ひ', u'び', u'ぴ', u'ふ', u'ぶ', u'ぷ', + u'へ', u'べ', u'ぺ', u'ほ', u'ぼ', u'ぽ', u'ま', u'み', + u'む', u'め', u'も', u'ゃ', u'や', u'ゅ', u'ゆ', u'ょ', + u'よ', u'ら', u'り', u'る', u'れ', u'ろ', u'ゎ', u'わ', + u'ゐ', u'ゑ', u'を', u'ん', u' ', u' ', u' ', u'ゝ', + u'ゞ', u'ー', u'。', u'「', u'」', u'、', u'・' +}; + +const char16_t KatakanaTable[94] = +{ + u'ァ', u'ア', u'ィ', u'イ', u'ゥ', u'ウ', u'ェ', + u'エ', u'ォ', u'オ', u'カ', u'ガ', u'キ', u'ギ', u'ク', + u'グ', u'ケ', u'ゲ', u'コ', u'ゴ', u'サ', u'ザ', u'シ', + u'ジ', u'ス', u'ズ', u'セ', u'ゼ', u'ソ', u'ゾ', u'タ', + u'ダ', u'チ', u'ヂ', u'ッ', u'ツ', u'ヅ', u'テ', u'デ', + u'ト', u'ド', u'ナ', u'ニ', u'ヌ', u'ネ', u'ノ', u'ハ', + u'バ', u'パ', u'ヒ', u'ビ', u'ピ', u'フ', u'ブ', u'プ', + u'ヘ', u'ベ', u'ペ', u'ホ', u'ボ', u'ポ', u'マ', u'ミ', + u'ム', u'メ', u'モ', u'ャ', u'ヤ', u'ュ', u'ユ', u'ョ', + u'ヨ', u'ラ', u'リ', u'ル', u'レ', u'ロ', u'ヮ', u'ワ', + u'ヰ', u'ヱ', u'ヲ', u'ン', u'ヴ', u'ヵ', u'ヶ', u'ヽ', + u'ヾ', u'ー', u'。', u'「', u'」', u'、', u'・' +}; + +const char16_t JisXKatakanaTable[94] = +{ + u'。', u'「', u'」', u'、', u'・', u'ヲ', u'ァ', + u'ィ', u'ゥ', u'ェ', u'ォ', u'ャ', u'ュ', u'ョ', u'ッ', + u'ー', u'ア', u'イ', u'ウ', u'エ', u'オ', u'カ', u'キ', + u'ク', u'ケ', u'コ', u'サ', u'シ', u'ス', u'セ', u'ソ', + u'タ', u'チ', u'ツ', u'テ', u'ト', u'ナ', u'ニ', u'ヌ', + u'ネ', u'ノ', u'ハ', u'ヒ', u'フ', u'ヘ', u'ホ', u'マ', + u'ミ', u'ム', u'メ', u'モ', u'ヤ', u'ユ', u'ヨ', u'ラ', + u'リ', u'ル', u'レ', u'ロ', u'ワ', u'ン', u'゛', u'゜', + u'�', u'�', u'�', u'�', u'�', u'�', u'�', u'�', + u'�', u'�', u'�', u'�', u'�', u'�', u'�', u'�', + u'�', u'�', u'�', u'�', u'�', u'�', u'�', u'�', + u'�', u'�', u'�', u'�', u'�', u'�', u'�' +}; + +const char16_t LatinExtensionTable[94] = +{ + u'¡', u'¢', u'£', u'€', u'¥', u'Š', u'§', + u'š', u'©', u'ª', u'«', u'¬', u'ÿ', u'®', u'¯', + u'°', u'±', u'²', u'³', u'Ž', u'μ', u'¶', u'·', + u'ž', u'¹', u'º', u'»', u'Œ', u'œ', u'Ÿ', u'¿', + u'À', u'Á', u'Â', u'Ã', u'Ä', u'Å', u'Æ', u'Ç', + u'È', u'É', u'Ê', u'Ë', u'Ì', u'Í', u'Î', u'Ï', + u'Ð', u'Ñ', u'Ò', u'Ó', u'Ô', u'Õ', u'Ö', u'×', + u'Ø', u'Ù', u'Ú', u'Û', u'Ü', u'Ý', u'Þ', u'ß', + u'à', u'á', u'â', u'ã', u'ä', u'å', u'æ', u'ç', + u'è', u'é', u'ê', u'ë', u'ì', u'í', u'î', u'ï', + u'ð', u'ñ', u'ò', u'ó', u'ô', u'õ', u'ö', u'÷', + u'ø', u'ù', u'ú', u'û', u'ü', u'ý', u'þ' +}; + +const char16_t LatinSpecialTable[94] = +{ + u'♪', u'�', u'�', u'�', u'�', u'�', u'�', + u'�', u'�', u'�', u'�', u'�', u'�', u'�', u'�', + u'¤', u'¦', u'¨', u'´', u'¸', u'¼', u'½', u'¾', + u'�', u'�', u'�', u'�', u'�', u'�', u'�', u'�', + u'…', u'█', u'‘', u'’', u'“', u'”', u'•', u'™', + u'⅛', u'⅜', u'⅝', u'⅞', u'�', u'�', u'�', u'�', + u'�', u'�', u'�', u'�', u'�', u'�', u'�', u'�', + u'�', u'�', u'�', u'�', u'�', u'�', u'�', u'�', + u'�', u'�', u'�', u'�', u'�', u'�', u'�', u'�', + u'�', u'�', u'�', u'�', u'�', u'�', u'�', u'�', + u'�', u'�', u'�', u'�', u'�', u'�', u'�', u'�', + u'�', u'�', u'�', u'�', u'�', u'�', u'�' +}; + +const char16_t JisTable[84 * 94 + 1] = + // row 1-84 (with STD-B24 Table E-1 Conversion) + u" 、。,.・:;?!゛゜̲́̀̈̂̅ヽヾゝゞ〃仝々〆〇ー―‐/\~∥|…‥‘’“”()〔〕[]{}〈〉《》「」『』【】+-±×÷=≠<>≦≧∞∴♂♀°′″℃¥$¢£%#&*@§☆★○●◎◇" + u"◆□■△▲▽▼※〒→←↑↓〓�����������∈∋⊆⊇⊂⊃∪∩��������∧∨¬⇒⇔∀∃�����������∠⊥⌒∂∇≡≒≪≫√∽∝∵∫∬�������ʼn♯♭♪†‡¶����⃝" + u"���������������0123456789�������ABCDEFGHIJKLMNOPQRSTUVWXYZ������abcdefghijklmnopqrstuvwxyz����" + u"ぁあぃいぅうぇえぉおかがきぎくぐけげこごさざしじすずせぜそぞただちぢっつづてでとどなにぬねのはばぱひびぴふぶぷへべぺほぼぽまみむめもゃやゅゆょよらりるれろゎわゐゑをん�����������" + u"ァアィイゥウェエォオカガキギクグケゲコゴサザシジスズセゼソゾタダチヂッツヅテデトドナニヌネノハバパヒビピフブプヘベペホボポマミムメモャヤュユョヨラリルレロヮワヰヱヲンヴヵヶ��������" + u"ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ��������αβγδεζηθικλμνξοπρστυφχψω��������������������������������������" + u"АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ���������������абвгдеёжзийклмнопрстуфхцчшщъыьэюя�������������" + u"─│┌┐┘└├┬┤┴┼━┃┏┓┛┗┣┳┫┻╋┠┯┨┷┿┝┰┥┸╂��������������������������������������������������������������" + u"����������������������������������������������������������������������������������������������" + u"����������������������������������������������������������������������������������������������" + u"����������������������������������������������������������������������������������������������" + u"����������������������������������������������������������������������������������������������" + u"����������������������������������������������������������������������������������������������" + u"����������������������������������������������������������������������������������������������" + u"����������������������������������������������������������������������������������������������" + u"亜唖娃阿哀愛挨姶逢葵茜穐悪握渥旭葦芦鯵梓圧斡扱宛姐虻飴絢綾鮎或粟袷安庵按暗案闇鞍杏以伊位依偉囲夷委威尉惟意慰易椅為畏異移維緯胃萎衣謂違遺医井亥域育郁磯一壱溢逸稲茨芋鰯允印咽員因姻引飲淫胤蔭" + u"院陰隠韻吋右宇烏羽迂雨卯鵜窺丑碓臼渦嘘唄欝蔚鰻姥厩浦瓜閏噂云運雲荏餌叡営嬰影映曳栄永泳洩瑛盈穎頴英衛詠鋭液疫益駅悦謁越閲榎厭円園堰奄宴延怨掩援沿演炎焔煙燕猿縁艶苑薗遠鉛鴛塩於汚甥凹央奥往応" + u"押旺横欧殴王翁襖鴬鴎黄岡沖荻億屋憶臆桶牡乙俺卸恩温穏音下化仮何伽価佳加可嘉夏嫁家寡科暇果架歌河火珂禍禾稼箇花苛茄荷華菓蝦課嘩貨迦過霞蚊俄峨我牙画臥芽蛾賀雅餓駕介会解回塊壊廻快怪悔恢懐戒拐改" + u"魁晦械海灰界皆絵芥蟹開階貝凱劾外咳害崖慨概涯碍蓋街該鎧骸浬馨蛙垣柿蛎鈎劃嚇各廓拡撹格核殻獲確穫覚角赫較郭閣隔革学岳楽額顎掛笠樫橿梶鰍潟割喝恰括活渇滑葛褐轄且鰹叶椛樺鞄株兜竃蒲釜鎌噛鴨栢茅萱" + u"粥刈苅瓦乾侃冠寒刊勘勧巻喚堪姦完官寛干幹患感慣憾換敢柑桓棺款歓汗漢澗潅環甘監看竿管簡緩缶翰肝艦莞観諌貫還鑑間閑関陥韓館舘丸含岸巌玩癌眼岩翫贋雁頑顔願企伎危喜器基奇嬉寄岐希幾忌揮机旗既期棋棄" + u"機帰毅気汽畿祈季稀紀徽規記貴起軌輝飢騎鬼亀偽儀妓宜戯技擬欺犠疑祇義蟻誼議掬菊鞠吉吃喫桔橘詰砧杵黍却客脚虐逆丘久仇休及吸宮弓急救朽求汲泣灸球究窮笈級糾給旧牛去居巨拒拠挙渠虚許距鋸漁禦魚亨享京" + u"供侠僑兇競共凶協匡卿叫喬境峡強彊怯恐恭挟教橋況狂狭矯胸脅興蕎郷鏡響饗驚仰凝尭暁業局曲極玉桐粁僅勤均巾錦斤欣欽琴禁禽筋緊芹菌衿襟謹近金吟銀九倶句区狗玖矩苦躯駆駈駒具愚虞喰空偶寓遇隅串櫛釧屑屈" + u"掘窟沓靴轡窪熊隈粂栗繰桑鍬勲君薫訓群軍郡卦袈祁係傾刑兄啓圭珪型契形径恵慶慧憩掲携敬景桂渓畦稽系経継繋罫茎荊蛍計詣警軽頚鶏芸迎鯨劇戟撃激隙桁傑欠決潔穴結血訣月件倹倦健兼券剣喧圏堅嫌建憲懸拳捲" + u"検権牽犬献研硯絹県肩見謙賢軒遣鍵険顕験鹸元原厳幻弦減源玄現絃舷言諺限乎個古呼固姑孤己庫弧戸故枯湖狐糊袴股胡菰虎誇跨鈷雇顧鼓五互伍午呉吾娯後御悟梧檎瑚碁語誤護醐乞鯉交佼侯候倖光公功効勾厚口向" + u"后喉坑垢好孔孝宏工巧巷幸広庚康弘恒慌抗拘控攻昂晃更杭校梗構江洪浩港溝甲皇硬稿糠紅紘絞綱耕考肯肱腔膏航荒行衡講貢購郊酵鉱砿鋼閤降項香高鴻剛劫号合壕拷濠豪轟麹克刻告国穀酷鵠黒獄漉腰甑忽惚骨狛込" + u"此頃今困坤墾婚恨懇昏昆根梱混痕紺艮魂些佐叉唆嵯左差査沙瑳砂詐鎖裟坐座挫債催再最哉塞妻宰彩才採栽歳済災采犀砕砦祭斎細菜裁載際剤在材罪財冴坂阪堺榊肴咲崎埼碕鷺作削咋搾昨朔柵窄策索錯桜鮭笹匙冊刷" + u"察拶撮擦札殺薩雑皐鯖捌錆鮫皿晒三傘参山惨撒散桟燦珊産算纂蚕讃賛酸餐斬暫残仕仔伺使刺司史嗣四士始姉姿子屍市師志思指支孜斯施旨枝止死氏獅祉私糸紙紫肢脂至視詞詩試誌諮資賜雌飼歯事似侍児字寺慈持時" + u"次滋治爾璽痔磁示而耳自蒔辞汐鹿式識鴫竺軸宍雫七叱執失嫉室悉湿漆疾質実蔀篠偲柴芝屡蕊縞舎写射捨赦斜煮社紗者謝車遮蛇邪借勺尺杓灼爵酌釈錫若寂弱惹主取守手朱殊狩珠種腫趣酒首儒受呪寿授樹綬需囚収周" + u"宗就州修愁拾洲秀秋終繍習臭舟蒐衆襲讐蹴輯週酋酬集醜什住充十従戎柔汁渋獣縦重銃叔夙宿淑祝縮粛塾熟出術述俊峻春瞬竣舜駿准循旬楯殉淳準潤盾純巡遵醇順処初所暑曙渚庶緒署書薯藷諸助叙女序徐恕鋤除傷償" + u"勝匠升召哨商唱嘗奨妾娼宵将小少尚庄床廠彰承抄招掌捷昇昌昭晶松梢樟樵沼消渉湘焼焦照症省硝礁祥称章笑粧紹肖菖蒋蕉衝裳訟証詔詳象賞醤鉦鍾鐘障鞘上丈丞乗冗剰城場壌嬢常情擾条杖浄状畳穣蒸譲醸錠嘱埴飾" + u"拭植殖燭織職色触食蝕辱尻伸信侵唇娠寝審心慎振新晋森榛浸深申疹真神秦紳臣芯薪親診身辛進針震人仁刃塵壬尋甚尽腎訊迅陣靭笥諏須酢図厨逗吹垂帥推水炊睡粋翠衰遂酔錐錘随瑞髄崇嵩数枢趨雛据杉椙菅頗雀裾" + u"澄摺寸世瀬畝是凄制勢姓征性成政整星晴棲栖正清牲生盛精聖声製西誠誓請逝醒青静斉税脆隻席惜戚斥昔析石積籍績脊責赤跡蹟碩切拙接摂折設窃節説雪絶舌蝉仙先千占宣専尖川戦扇撰栓栴泉浅洗染潜煎煽旋穿箭線" + u"繊羨腺舛船薦詮賎践選遷銭銑閃鮮前善漸然全禅繕膳糎噌塑岨措曾曽楚狙疏疎礎祖租粗素組蘇訴阻遡鼠僧創双叢倉喪壮奏爽宋層匝惣想捜掃挿掻操早曹巣槍槽漕燥争痩相窓糟総綜聡草荘葬蒼藻装走送遭鎗霜騒像増憎" + u"臓蔵贈造促側則即息捉束測足速俗属賊族続卒袖其揃存孫尊損村遜他多太汰詑唾堕妥惰打柁舵楕陀駄騨体堆対耐岱帯待怠態戴替泰滞胎腿苔袋貸退逮隊黛鯛代台大第醍題鷹滝瀧卓啄宅托択拓沢濯琢託鐸濁諾茸凧蛸只" + u"叩但達辰奪脱巽竪辿棚谷狸鱈樽誰丹単嘆坦担探旦歎淡湛炭短端箪綻耽胆蛋誕鍛団壇弾断暖檀段男談値知地弛恥智池痴稚置致蜘遅馳築畜竹筑蓄逐秩窒茶嫡着中仲宙忠抽昼柱注虫衷註酎鋳駐樗瀦猪苧著貯丁兆凋喋寵" + u"帖帳庁弔張彫徴懲挑暢朝潮牒町眺聴脹腸蝶調諜超跳銚長頂鳥勅捗直朕沈珍賃鎮陳津墜椎槌追鎚痛通塚栂掴槻佃漬柘辻蔦綴鍔椿潰坪壷嬬紬爪吊釣鶴亭低停偵剃貞呈堤定帝底庭廷弟悌抵挺提梯汀碇禎程締艇訂諦蹄逓" + u"邸鄭釘鼎泥摘擢敵滴的笛適鏑溺哲徹撤轍迭鉄典填天展店添纏甜貼転顛点伝殿澱田電兎吐堵塗妬屠徒斗杜渡登菟賭途都鍍砥砺努度土奴怒倒党冬凍刀唐塔塘套宕島嶋悼投搭東桃梼棟盗淘湯涛灯燈当痘祷等答筒糖統到" + u"董蕩藤討謄豆踏逃透鐙陶頭騰闘働動同堂導憧撞洞瞳童胴萄道銅峠鴇匿得徳涜特督禿篤毒独読栃橡凸突椴届鳶苫寅酉瀞噸屯惇敦沌豚遁頓呑曇鈍奈那内乍凪薙謎灘捺鍋楢馴縄畷南楠軟難汝二尼弐迩匂賑肉虹廿日乳入" + u"如尿韮任妊忍認濡禰祢寧葱猫熱年念捻撚燃粘乃廼之埜嚢悩濃納能脳膿農覗蚤巴把播覇杷波派琶破婆罵芭馬俳廃拝排敗杯盃牌背肺輩配倍培媒梅楳煤狽買売賠陪這蝿秤矧萩伯剥博拍柏泊白箔粕舶薄迫曝漠爆縛莫駁麦" + u"函箱硲箸肇筈櫨幡肌畑畠八鉢溌発醗髪伐罰抜筏閥鳩噺塙蛤隼伴判半反叛帆搬斑板氾汎版犯班畔繁般藩販範釆煩頒飯挽晩番盤磐蕃蛮匪卑否妃庇彼悲扉批披斐比泌疲皮碑秘緋罷肥被誹費避非飛樋簸備尾微枇毘琵眉美" + u"鼻柊稗匹疋髭彦膝菱肘弼必畢筆逼桧姫媛紐百謬俵彪標氷漂瓢票表評豹廟描病秒苗錨鋲蒜蛭鰭品彬斌浜瀕貧賓頻敏瓶不付埠夫婦富冨布府怖扶敷斧普浮父符腐膚芙譜負賦赴阜附侮撫武舞葡蕪部封楓風葺蕗伏副復幅服" + u"福腹複覆淵弗払沸仏物鮒分吻噴墳憤扮焚奮粉糞紛雰文聞丙併兵塀幣平弊柄並蔽閉陛米頁僻壁癖碧別瞥蔑箆偏変片篇編辺返遍便勉娩弁鞭保舗鋪圃捕歩甫補輔穂募墓慕戊暮母簿菩倣俸包呆報奉宝峰峯崩庖抱捧放方朋" + u"法泡烹砲縫胞芳萌蓬蜂褒訪豊邦鋒飽鳳鵬乏亡傍剖坊妨帽忘忙房暴望某棒冒紡肪膨謀貌貿鉾防吠頬北僕卜墨撲朴牧睦穆釦勃没殆堀幌奔本翻凡盆摩磨魔麻埋妹昧枚毎哩槙幕膜枕鮪柾鱒桝亦俣又抹末沫迄侭繭麿万慢満" + u"漫蔓味未魅巳箕岬密蜜湊蓑稔脈妙粍民眠務夢無牟矛霧鵡椋婿娘冥名命明盟迷銘鳴姪牝滅免棉綿緬面麺摸模茂妄孟毛猛盲網耗蒙儲木黙目杢勿餅尤戻籾貰問悶紋門匁也冶夜爺耶野弥矢厄役約薬訳躍靖柳薮鑓愉愈油癒" + u"諭輸唯佑優勇友宥幽悠憂揖有柚湧涌猶猷由祐裕誘遊邑郵雄融夕予余与誉輿預傭幼妖容庸揚揺擁曜楊様洋溶熔用窯羊耀葉蓉要謡踊遥陽養慾抑欲沃浴翌翼淀羅螺裸来莱頼雷洛絡落酪乱卵嵐欄濫藍蘭覧利吏履李梨理璃" + u"痢裏裡里離陸律率立葎掠略劉流溜琉留硫粒隆竜龍侶慮旅虜了亮僚両凌寮料梁涼猟療瞭稜糧良諒遼量陵領力緑倫厘林淋燐琳臨輪隣鱗麟瑠塁涙累類令伶例冷励嶺怜玲礼苓鈴隷零霊麗齢暦歴列劣烈裂廉恋憐漣煉簾練聯" + u"蓮連錬呂魯櫓炉賂路露労婁廊弄朗楼榔浪漏牢狼篭老聾蝋郎六麓禄肋録論倭和話歪賄脇惑枠鷲亙亘鰐詫藁蕨椀湾碗腕�������������������������������������������" + u"弌丐丕个丱丶丼丿乂乖乘亂亅豫亊舒弍于亞亟亠亢亰亳亶从仍仄仆仂仗仞仭仟价伉佚估佛佝佗佇佶侈侏侘佻佩佰侑佯來侖儘俔俟俎俘俛俑俚俐俤俥倚倨倔倪倥倅伜俶倡倩倬俾俯們倆偃假會偕偐偈做偖偬偸傀傚傅傴傲" + u"僉僊傳僂僖僞僥僭僣僮價僵儉儁儂儖儕儔儚儡儺儷儼儻儿兀兒兌兔兢竸兩兪兮冀冂囘册冉冏冑冓冕冖冤冦冢冩冪冫决冱冲冰况冽凅凉凛几處凩凭凰凵凾刄刋刔刎刧刪刮刳刹剏剄剋剌剞剔剪剴剩剳剿剽劍劔劒剱劈劑辨" + u"辧劬劭劼劵勁勍勗勞勣勦飭勠勳勵勸勹匆匈甸匍匐匏匕匚匣匯匱匳匸區卆卅丗卉卍凖卞卩卮夘卻卷厂厖厠厦厥厮厰厶參簒雙叟曼燮叮叨叭叺吁吽呀听吭吼吮吶吩吝呎咏呵咎呟呱呷呰咒呻咀呶咄咐咆哇咢咸咥咬哄哈咨" + u"咫哂咤咾咼哘哥哦唏唔哽哮哭哺哢唹啀啣啌售啜啅啖啗唸唳啝喙喀咯喊喟啻啾喘喞單啼喃喩喇喨嗚嗅嗟嗄嗜嗤嗔嘔嗷嘖嗾嗽嘛嗹噎噐營嘴嘶嘲嘸噫噤嘯噬噪嚆嚀嚊嚠嚔嚏嚥嚮嚶嚴囂嚼囁囃囀囈囎囑囓囗囮囹圀囿圄圉" + u"圈國圍圓團圖嗇圜圦圷圸坎圻址坏坩埀垈坡坿垉垓垠垳垤垪垰埃埆埔埒埓堊埖埣堋堙堝塲堡塢塋塰毀塒堽塹墅墹墟墫墺壞墻墸墮壅壓壑壗壙壘壥壜壤壟壯壺壹壻壼壽夂夊夐夛梦夥夬夭夲夸夾竒奕奐奎奚奘奢奠奧奬奩" + u"奸妁妝佞侫妣妲姆姨姜妍姙姚娥娟娑娜娉娚婀婬婉娵娶婢婪媚媼媾嫋嫂媽嫣嫗嫦嫩嫖嫺嫻嬌嬋嬖嬲嫐嬪嬶嬾孃孅孀孑孕孚孛孥孩孰孳孵學斈孺宀它宦宸寃寇寉寔寐寤實寢寞寥寫寰寶寳尅將專對尓尠尢尨尸尹屁屆屎屓" + u"屐屏孱屬屮乢屶屹岌岑岔妛岫岻岶岼岷峅岾峇峙峩峽峺峭嶌峪崋崕崗嵜崟崛崑崔崢崚崙崘嵌嵒嵎嵋嵬嵳嵶嶇嶄嶂嶢嶝嶬嶮嶽嶐嶷嶼巉巍巓巒巖巛巫已巵帋帚帙帑帛帶帷幄幃幀幎幗幔幟幢幤幇幵并幺麼广庠廁廂廈廐廏" + u"廖廣廝廚廛廢廡廨廩廬廱廳廰廴廸廾弃弉彝彜弋弑弖弩弭弸彁彈彌彎弯彑彖彗彙彡彭彳彷徃徂彿徊很徑徇從徙徘徠徨徭徼忖忻忤忸忱忝悳忿怡恠怙怐怩怎怱怛怕怫怦怏怺恚恁恪恷恟恊恆恍恣恃恤恂恬恫恙悁悍惧悃悚" + u"悄悛悖悗悒悧悋惡悸惠惓悴忰悽惆悵惘慍愕愆惶惷愀惴惺愃愡惻惱愍愎慇愾愨愧慊愿愼愬愴愽慂慄慳慷慘慙慚慫慴慯慥慱慟慝慓慵憙憖憇憬憔憚憊憑憫憮懌懊應懷懈懃懆憺懋罹懍懦懣懶懺懴懿懽懼懾戀戈戉戍戌戔戛" + u"戞戡截戮戰戲戳扁扎扞扣扛扠扨扼抂抉找抒抓抖拔抃抔拗拑抻拏拿拆擔拈拜拌拊拂拇抛拉挌拮拱挧挂挈拯拵捐挾捍搜捏掖掎掀掫捶掣掏掉掟掵捫捩掾揩揀揆揣揉插揶揄搖搴搆搓搦搶攝搗搨搏摧摯摶摎攪撕撓撥撩撈撼" + u"據擒擅擇撻擘擂擱擧舉擠擡抬擣擯攬擶擴擲擺攀擽攘攜攅攤攣攫攴攵攷收攸畋效敖敕敍敘敞敝敲數斂斃變斛斟斫斷旃旆旁旄旌旒旛旙无旡旱杲昊昃旻杳昵昶昴昜晏晄晉晁晞晝晤晧晨晟晢晰暃暈暎暉暄暘暝曁暹曉暾暼" + u"曄暸曖曚曠昿曦曩曰曵曷朏朖朞朦朧霸朮朿朶杁朸朷杆杞杠杙杣杤枉杰枩杼杪枌枋枦枡枅枷柯枴柬枳柩枸柤柞柝柢柮枹柎柆柧檜栞框栩桀桍栲桎梳栫桙档桷桿梟梏梭梔條梛梃檮梹桴梵梠梺椏梍桾椁棊椈棘椢椦棡椌棍" + u"棔棧棕椶椒椄棗棣椥棹棠棯椨椪椚椣椡棆楹楷楜楸楫楔楾楮椹楴椽楙椰楡楞楝榁楪榲榮槐榿槁槓榾槎寨槊槝榻槃榧樮榑榠榜榕榴槞槨樂樛槿權槹槲槧樅榱樞槭樔槫樊樒櫁樣樓橄樌橲樶橸橇橢橙橦橈樸樢檐檍檠檄檢檣" + u"檗蘗檻櫃櫂檸檳檬櫞櫑櫟檪櫚櫪櫻欅蘖櫺欒欖鬱欟欸欷盜欹飮歇歃歉歐歙歔歛歟歡歸歹歿殀殄殃殍殘殕殞殤殪殫殯殲殱殳殷殼毆毋毓毟毬毫毳毯麾氈氓气氛氤氣汞汕汢汪沂沍沚沁沛汾汨汳沒沐泄泱泓沽泗泅泝沮沱沾" + u"沺泛泯泙泪洟衍洶洫洽洸洙洵洳洒洌浣涓浤浚浹浙涎涕濤涅淹渕渊涵淇淦涸淆淬淞淌淨淒淅淺淙淤淕淪淮渭湮渮渙湲湟渾渣湫渫湶湍渟湃渺湎渤滿渝游溂溪溘滉溷滓溽溯滄溲滔滕溏溥滂溟潁漑灌滬滸滾漿滲漱滯漲滌" + u"漾漓滷澆潺潸澁澀潯潛濳潭澂潼潘澎澑濂潦澳澣澡澤澹濆澪濟濕濬濔濘濱濮濛瀉瀋濺瀑瀁瀏濾瀛瀚潴瀝瀘瀟瀰瀾瀲灑灣炙炒炯烱炬炸炳炮烟烋烝烙焉烽焜焙煥煕熈煦煢煌煖煬熏燻熄熕熨熬燗熹熾燒燉燔燎燠燬燧燵燼" + u"燹燿爍爐爛爨爭爬爰爲爻爼爿牀牆牋牘牴牾犂犁犇犒犖犢犧犹犲狃狆狄狎狒狢狠狡狹狷倏猗猊猜猖猝猴猯猩猥猾獎獏默獗獪獨獰獸獵獻獺珈玳珎玻珀珥珮珞璢琅瑯琥珸琲琺瑕琿瑟瑙瑁瑜瑩瑰瑣瑪瑶瑾璋璞璧瓊瓏瓔珱" + u"瓠瓣瓧瓩瓮瓲瓰瓱瓸瓷甄甃甅甌甎甍甕甓甞甦甬甼畄畍畊畉畛畆畚畩畤畧畫畭畸當疆疇畴疊疉疂疔疚疝疥疣痂疳痃疵疽疸疼疱痍痊痒痙痣痞痾痿痼瘁痰痺痲痳瘋瘍瘉瘟瘧瘠瘡瘢瘤瘴瘰瘻癇癈癆癜癘癡癢癨癩癪癧癬癰" + u"癲癶癸發皀皃皈皋皎皖皓皙皚皰皴皸皹皺盂盍盖盒盞盡盥盧盪蘯盻眈眇眄眩眤眞眥眦眛眷眸睇睚睨睫睛睥睿睾睹瞎瞋瞑瞠瞞瞰瞶瞹瞿瞼瞽瞻矇矍矗矚矜矣矮矼砌砒礦砠礪硅碎硴碆硼碚碌碣碵碪碯磑磆磋磔碾碼磅磊磬" + u"磧磚磽磴礇礒礑礙礬礫祀祠祗祟祚祕祓祺祿禊禝禧齋禪禮禳禹禺秉秕秧秬秡秣稈稍稘稙稠稟禀稱稻稾稷穃穗穉穡穢穩龝穰穹穽窈窗窕窘窖窩竈窰窶竅竄窿邃竇竊竍竏竕竓站竚竝竡竢竦竭竰笂笏笊笆笳笘笙笞笵笨笶筐" + u"筺笄筍笋筌筅筵筥筴筧筰筱筬筮箝箘箟箍箜箚箋箒箏筝箙篋篁篌篏箴篆篝篩簑簔篦篥籠簀簇簓篳篷簗簍篶簣簧簪簟簷簫簽籌籃籔籏籀籐籘籟籤籖籥籬籵粃粐粤粭粢粫粡粨粳粲粱粮粹粽糀糅糂糘糒糜糢鬻糯糲糴糶糺紆" + u"紂紜紕紊絅絋紮紲紿紵絆絳絖絎絲絨絮絏絣經綉絛綏絽綛綺綮綣綵緇綽綫總綢綯緜綸綟綰緘緝緤緞緻緲緡縅縊縣縡縒縱縟縉縋縢繆繦縻縵縹繃縷縲縺繧繝繖繞繙繚繹繪繩繼繻纃緕繽辮繿纈纉續纒纐纓纔纖纎纛纜缸缺" + u"罅罌罍罎罐网罕罔罘罟罠罨罩罧罸羂羆羃羈羇羌羔羞羝羚羣羯羲羹羮羶羸譱翅翆翊翕翔翡翦翩翳翹飜耆耄耋耒耘耙耜耡耨耿耻聊聆聒聘聚聟聢聨聳聲聰聶聹聽聿肄肆肅肛肓肚肭冐肬胛胥胙胝胄胚胖脉胯胱脛脩脣脯腋" + u"隋腆脾腓腑胼腱腮腥腦腴膃膈膊膀膂膠膕膤膣腟膓膩膰膵膾膸膽臀臂膺臉臍臑臙臘臈臚臟臠臧臺臻臾舁舂舅與舊舍舐舖舩舫舸舳艀艙艘艝艚艟艤艢艨艪艫舮艱艷艸艾芍芒芫芟芻芬苡苣苟苒苴苳苺莓范苻苹苞茆苜茉苙" + u"茵茴茖茲茱荀茹荐荅茯茫茗茘莅莚莪莟莢莖茣莎莇莊荼莵荳荵莠莉莨菴萓菫菎菽萃菘萋菁菷萇菠菲萍萢萠莽萸蔆菻葭萪萼蕚蒄葷葫蒭葮蒂葩葆萬葯葹萵蓊葢蒹蒿蒟蓙蓍蒻蓚蓐蓁蓆蓖蒡蔡蓿蓴蔗蔘蔬蔟蔕蔔蓼蕀蕣蕘蕈" + u"蕁蘂蕋蕕薀薤薈薑薊薨蕭薔薛藪薇薜蕷蕾薐藉薺藏薹藐藕藝藥藜藹蘊蘓蘋藾藺蘆蘢蘚蘰蘿虍乕虔號虧虱蚓蚣蚩蚪蚋蚌蚶蚯蛄蛆蚰蛉蠣蚫蛔蛞蛩蛬蛟蛛蛯蜒蜆蜈蜀蜃蛻蜑蜉蜍蛹蜊蜴蜿蜷蜻蜥蜩蜚蝠蝟蝸蝌蝎蝴蝗蝨蝮蝙" + u"蝓蝣蝪蠅螢螟螂螯蟋螽蟀蟐雖螫蟄螳蟇蟆螻蟯蟲蟠蠏蠍蟾蟶蟷蠎蟒蠑蠖蠕蠢蠡蠱蠶蠹蠧蠻衄衂衒衙衞衢衫袁衾袞衵衽袵衲袂袗袒袮袙袢袍袤袰袿袱裃裄裔裘裙裝裹褂裼裴裨裲褄褌褊褓襃褞褥褪褫襁襄褻褶褸襌褝襠襞" + u"襦襤襭襪襯襴襷襾覃覈覊覓覘覡覩覦覬覯覲覺覽覿觀觚觜觝觧觴觸訃訖訐訌訛訝訥訶詁詛詒詆詈詼詭詬詢誅誂誄誨誡誑誥誦誚誣諄諍諂諚諫諳諧諤諱謔諠諢諷諞諛謌謇謚諡謖謐謗謠謳鞫謦謫謾謨譁譌譏譎證譖譛譚譫" + u"譟譬譯譴譽讀讌讎讒讓讖讙讚谺豁谿豈豌豎豐豕豢豬豸豺貂貉貅貊貍貎貔豼貘戝貭貪貽貲貳貮貶賈賁賤賣賚賽賺賻贄贅贊贇贏贍贐齎贓賍贔贖赧赭赱赳趁趙跂趾趺跏跚跖跌跛跋跪跫跟跣跼踈踉跿踝踞踐踟蹂踵踰踴蹊" + u"蹇蹉蹌蹐蹈蹙蹤蹠踪蹣蹕蹶蹲蹼躁躇躅躄躋躊躓躑躔躙躪躡躬躰軆躱躾軅軈軋軛軣軼軻軫軾輊輅輕輒輙輓輜輟輛輌輦輳輻輹轅轂輾轌轉轆轎轗轜轢轣轤辜辟辣辭辯辷迚迥迢迪迯邇迴逅迹迺逑逕逡逍逞逖逋逧逶逵逹迸" + u"遏遐遑遒逎遉逾遖遘遞遨遯遶隨遲邂遽邁邀邊邉邏邨邯邱邵郢郤扈郛鄂鄒鄙鄲鄰酊酖酘酣酥酩酳酲醋醉醂醢醫醯醪醵醴醺釀釁釉釋釐釖釟釡釛釼釵釶鈞釿鈔鈬鈕鈑鉞鉗鉅鉉鉤鉈銕鈿鉋鉐銜銖銓銛鉚鋏銹銷鋩錏鋺鍄錮" + u"錙錢錚錣錺錵錻鍜鍠鍼鍮鍖鎰鎬鎭鎔鎹鏖鏗鏨鏥鏘鏃鏝鏐鏈鏤鐚鐔鐓鐃鐇鐐鐶鐫鐵鐡鐺鑁鑒鑄鑛鑠鑢鑞鑪鈩鑰鑵鑷鑽鑚鑼鑾钁鑿閂閇閊閔閖閘閙閠閨閧閭閼閻閹閾闊濶闃闍闌闕闔闖關闡闥闢阡阨阮阯陂陌陏陋陷陜陞" + u"陝陟陦陲陬隍隘隕隗險隧隱隲隰隴隶隸隹雎雋雉雍襍雜霍雕雹霄霆霈霓霎霑霏霖霙霤霪霰霹霽霾靄靆靈靂靉靜靠靤靦靨勒靫靱靹鞅靼鞁靺鞆鞋鞏鞐鞜鞨鞦鞣鞳鞴韃韆韈韋韜韭齏韲竟韶韵頏頌頸頤頡頷頽顆顏顋顫顯顰" + u"顱顴顳颪颯颱颶飄飃飆飩飫餃餉餒餔餘餡餝餞餤餠餬餮餽餾饂饉饅饐饋饑饒饌饕馗馘馥馭馮馼駟駛駝駘駑駭駮駱駲駻駸騁騏騅駢騙騫騷驅驂驀驃騾驕驍驛驗驟驢驥驤驩驫驪骭骰骼髀髏髑髓體髞髟髢髣髦髯髫髮髴髱髷" + u"髻鬆鬘鬚鬟鬢鬣鬥鬧鬨鬩鬪鬮鬯鬲魄魃魏魍魎魑魘魴鮓鮃鮑鮖鮗鮟鮠鮨鮴鯀鯊鮹鯆鯏鯑鯒鯣鯢鯤鯔鯡鰺鯲鯱鯰鰕鰔鰉鰓鰌鰆鰈鰒鰊鰄鰮鰛鰥鰤鰡鰰鱇鰲鱆鰾鱚鱠鱧鱶鱸鳧鳬鳰鴉鴈鳫鴃鴆鴪鴦鶯鴣鴟鵄鴕鴒鵁鴿鴾鵆鵈" + u"鵝鵞鵤鵑鵐鵙鵲鶉鶇鶫鵯鵺鶚鶤鶩鶲鷄鷁鶻鶸鶺鷆鷏鷂鷙鷓鷸鷦鷭鷯鷽鸚鸛鸞鹵鹹鹽麁麈麋麌麒麕麑麝麥麩麸麪麭靡黌黎黏黐黔黜點黝黠黥黨黯黴黶黷黹黻黼黽鼇鼈皷鼕鼡鼬鼾齊齒齔齣齟齠齡齦齧齬齪齷齲齶龕龜龠" + u"堯槇遙瑤凜熙����������������������������������������������������������������������������������������"; + +const char32_t GaijiTable[7 * 94 + 1] = + // row 90-94 + U"⛌⛍❗⛏⛐⛑⛒⛕⛓⛔🅿🆊⛖⛗⛘⛙⛚⛛⛜⛝⛞⛟⛠⛡⭕㉈㉉㉊㉋㉌㉍㉎㉏⒑⒒⒓🅊🅌🄿🅆🅋🈐🈑🈒🈓🅂🈔🈕🈖🅍🄱🄽⬛⬤🈗🈘🈙🈚🈛⚿🈜🈝🈞🈟🈠🈡🈢🈣🈤🈥🅎㊙🈀" + U"⛣⭖⭗⭘⭙☓㊋〒⛨㉆㉅⛩࿖⛪⛫⛬♨⛭⛮⛯⚓✈⛰⛱⛲⛳⛴⛵🅗ⒹⓈ⛶🅟🆋🆍🆌🅹⛷⛸⛹⛺🅻☎⛻⛼⛽⛾🅼⛿" + U"➡⬅⬆⬇⬯⬮㎡㎥㎝㎠㎤🄀⒈⒉⒊⒋⒌⒍⒎⒏⒐🄁🄂🄃🄄🄅🄆🄇🄈🄉🄊㈳㈶㈲㈱㈹㉄▶◀〖〗⟐²³🄭🄬🄫㉇🆐🈦℻" + U"㈪㈫㈬㈭㈮㈯㈰㈷㍾㍽㍼㍻№℡〶⚾🉀🉁🉂🉃🉄🉅🉆🉇🉈🄪🈧🈨🈩🈔🈪🈫🈬🈭🈮🈯🈰🈱ℓ㎏㎐㏊㎞㎢㍱½↉⅓⅔¼¾⅕⅖⅗⅘⅙⅚⅐⅛⅑⅒☀☁☂⛄☖☗⛉⛊♦♥♣♠⛋⨀‼⁉⛅☔⛆☃⛇⚡⛈⚞⚟♬" + U"ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ⑰⑱⑲⑳⑴⑵⑶⑷⑸⑹⑺⑻⑼⑽⑾⑿㉑㉒㉓㉔🄐🄑🄒🄓🄔🄕🄖🄗🄘🄙🄚🄛🄜🄝🄞🄟🄠🄡🄢🄣🄤🄥🄦🄧🄨🄩㉕㉖㉗㉘㉙㉚①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯❶❷❸❹❺❻❼❽❾❿⓫⓬㉛" + // row 85-86 + U"㐂𠅘份仿侚俉傜儞冼㔟匇卡卬詹𠮷呍咖咜咩唎啊噲囤圳圴塚墀姤娣婕寬﨑㟢庬弴彅德怗恵愰昤曈曙曺曻桒鿄椑椻橅檑櫛𣏌𣏾𣗄毱泠洮海涿淊淸渚潞濹灤𤋮𤋮煇燁爀玟玨珉珖琛琡琢琦琪琬琹瑋㻚畵疁睲䂓磈磠祇禮鿆䄃" + U"鿅秚稞筿簱䉤綋羡脘脺舘芮葛蓜蓬蕙藎蝕蟬蠋裵角諶跎辻迶郝鄧鄭醲鈳銈錡鍈閒雞餃饀髙鯖鷗麴麵"; + +const uint8_t DefaultMacro[16][20] = +{ + {0x1b, 0x24, 0x42, 0x1b, 0x29, 0x4a, 0x1b, 0x2a, 0x30, 0x1b, 0x2b, 0x20, 0x70, 0x0f, 0x1b, 0x7d}, + {0x1b, 0x24, 0x42, 0x1b, 0x29, 0x31, 0x1b, 0x2a, 0x30, 0x1b, 0x2b, 0x20, 0x70, 0x0f, 0x1b, 0x7d}, + {0x1b, 0x24, 0x42, 0x1b, 0x29, 0x20, 0x41, 0x1b, 0x2a, 0x30, 0x1b, 0x2b, 0x20, 0x70, 0x0f, 0x1b, 0x7d}, + {0x1b, 0x28, 0x32, 0x1b, 0x29, 0x34, 0x1b, 0x2a, 0x35, 0x1b, 0x2b, 0x20, 0x70, 0x0f, 0x1b, 0x7d}, + {0x1b, 0x28, 0x32, 0x1b, 0x29, 0x33, 0x1b, 0x2a, 0x35, 0x1b, 0x2b, 0x20, 0x70, 0x0f, 0x1b, 0x7d}, + {0x1b, 0x28, 0x32, 0x1b, 0x29, 0x20, 0x41, 0x1b, 0x2a, 0x35, 0x1b, 0x2b, 0x20, 0x70, 0x0f, 0x1b, 0x7d}, + {0x1b, 0x28, 0x20, 0x41, 0x1b, 0x29, 0x20, 0x42, 0x1b, 0x2a, 0x20, 0x43, 0x1b, 0x2b, 0x20, 0x70, 0x0f, 0x1b, 0x7d}, + {0x1b, 0x28, 0x20, 0x44, 0x1b, 0x29, 0x20, 0x45, 0x1b, 0x2a, 0x20, 0x46, 0x1b, 0x2b, 0x20, 0x70, 0x0f, 0x1b, 0x7d}, + {0x1b, 0x28, 0x20, 0x47, 0x1b, 0x29, 0x20, 0x48, 0x1b, 0x2a, 0x20, 0x49, 0x1b, 0x2b, 0x20, 0x70, 0x0f, 0x1b, 0x7d}, + {0x1b, 0x28, 0x20, 0x4a, 0x1b, 0x29, 0x20, 0x4b, 0x1b, 0x2a, 0x20, 0x4c, 0x1b, 0x2b, 0x20, 0x70, 0x0f, 0x1b, 0x7d}, + {0x1b, 0x28, 0x20, 0x4d, 0x1b, 0x29, 0x20, 0x4e, 0x1b, 0x2a, 0x20, 0x4f, 0x1b, 0x2b, 0x20, 0x70, 0x0f, 0x1b, 0x7d}, + {0x1b, 0x24, 0x42, 0x1b, 0x29, 0x20, 0x42, 0x1b, 0x2a, 0x30, 0x1b, 0x2b, 0x20, 0x70, 0x0f, 0x1b, 0x7d}, + {0x1b, 0x24, 0x42, 0x1b, 0x29, 0x20, 0x43, 0x1b, 0x2a, 0x30, 0x1b, 0x2b, 0x20, 0x70, 0x0f, 0x1b, 0x7d}, + {0x1b, 0x24, 0x42, 0x1b, 0x29, 0x20, 0x44, 0x1b, 0x2a, 0x30, 0x1b, 0x2b, 0x20, 0x70, 0x0f, 0x1b, 0x7d}, + {0x1b, 0x28, 0x31, 0x1b, 0x29, 0x30, 0x1b, 0x2a, 0x4a, 0x1b, 0x2b, 0x20, 0x70, 0x0f, 0x1b, 0x7d}, + {0x1b, 0x28, 0x4a, 0x1b, 0x29, 0x32, 0x1b, 0x2a, 0x20, 0x41, 0x1b, 0x2b, 0x20, 0x70, 0x0f, 0x1b, 0x7d} +}; +} diff --git a/tsreadex/traceb24.hpp b/tsreadex/traceb24.hpp new file mode 100644 index 0000000..1c07a39 --- /dev/null +++ b/tsreadex/traceb24.hpp @@ -0,0 +1,60 @@ +#ifndef INCLUDE_TRACEB24_HPP +#define INCLUDE_TRACEB24_HPP + +#include "util.hpp" +#include +#include +#include +#include + +class CTraceB24Caption +{ +public: + CTraceB24Caption(); + void AddPacket(const uint8_t *packet); + void SetFile(FILE *fp) { m_fp = fp; } + +private: + enum LANG_TAG_TYPE + { + LANG_TAG_ABSENT, + LANG_TAG_UNKNOWN, + LANG_TAG_UCS, + LANG_TAG_ARIB8, + LANG_TAG_ARIB8_LATIN, + }; + + enum PARSE_PRIVATE_DATA_RESULT + { + PARSE_PRIVATE_DATA_SUCCEEDED, + PARSE_PRIVATE_DATA_FAILED, + PARSE_PRIVATE_DATA_FAILED_CRC, + PARSE_PRIVATE_DATA_FAILED_NEED_MANAGEMENT, + PARSE_PRIVATE_DATA_FAILED_UNSUPPORTED, + }; + + void CheckPmt(const PSI &psi); + void OutputPrivateDataPes(const std::vector &pes, + std::vector &drcsList, LANG_TAG_TYPE (&langTags)[8]); + static PARSE_PRIVATE_DATA_RESULT ParsePrivateData(std::vector &buf, std::vector &textPosList, const uint8_t *data, size_t dataSize, + std::vector &drcsList, LANG_TAG_TYPE (&langTags)[8]); + + FILE *m_fp; + PAT m_pat; + int m_firstPmtPid; + PSI m_firstPmtPsi; + int m_captionPid; + int m_superimposePid; + std::pair> m_captionPes; + std::pair> m_superimposePes; + std::vector m_captionDrcsList; + std::vector m_superimposeDrcsList; + LANG_TAG_TYPE m_captionLangTags[8]; + LANG_TAG_TYPE m_superimposeLangTags[8]; + int m_pcrPid; + int64_t m_pcr; + std::vector m_buf; + std::vector m_intBuf; +}; + +#endif diff --git a/tsreadex/tsreadex.cpp b/tsreadex/tsreadex.cpp new file mode 100644 index 0000000..064bdcc --- /dev/null +++ b/tsreadex/tsreadex.cpp @@ -0,0 +1,517 @@ +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include +#include +#else +#ifndef _FILE_OFFSET_BITS +#define _FILE_OFFSET_BITS 64 +#endif +#include +#include +#include +#include +#include +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include "id3conv.hpp" +#include "servicefilter.hpp" +#include "traceb24.hpp" +#include "util.hpp" + +namespace +{ +void SleepFor(std::chrono::milliseconds rel) +{ +#ifdef _WIN32 + // MSVC sleep_for() is buggy + Sleep(static_cast(rel.count())); +#else + std::this_thread::sleep_for(rel); +#endif +} + +#ifdef _WIN32 +const char *GetSmallString(const wchar_t *s) +{ + static char ss[32]; + size_t i = 0; + for (; i < sizeof(ss) - 1 && s[i]; ++i) { + ss[i] = 0 < s[i] && s[i] <= 127 ? static_cast(s[i]) : '?'; + } + ss[i] = '\0'; + return ss; +} + +int64_t SeekFile(HANDLE file, int64_t offset) +{ + LARGE_INTEGER li; + li.QuadPart = offset < 0 ? offset + 1 : offset; + return SetFilePointerEx(file, li, &li, offset < 0 ? FILE_END : FILE_BEGIN) ? li.QuadPart : -1; +} + +template +int ReadFileToBuffer(HANDLE file, uint8_t *buf, size_t count, HANDLE asyncContext, P asyncCancelProc) +{ + OVERLAPPED ol = {}; + DWORD nRead; + if (asyncContext) { + // For asynchronous, returns -1 for EOF + ol.hEvent = asyncContext; + if (ReadFile(file, buf, static_cast(count), nullptr, &ol)) { + return GetOverlappedResult(file, &ol, &nRead, FALSE) ? nRead : -1; + } + else if (GetLastError() == ERROR_IO_PENDING) { + while (!asyncCancelProc()) { + if (WaitForSingleObject(asyncContext, 1000) != WAIT_TIMEOUT) { + return GetOverlappedResult(file, &ol, &nRead, FALSE) ? nRead : -1; + } + } + CancelIo(file); + WaitForSingleObject(asyncContext, INFINITE); + } + } + else if (ReadFile(file, buf, static_cast(count), &nRead, nullptr)) { + // For synchronous, 0 means EOF + return nRead; + } + return -1; +} + +void CloseFile(HANDLE file, HANDLE asyncContext) +{ + if (file != INVALID_HANDLE_VALUE) { + CloseHandle(file); + } + if (asyncContext) { + CloseHandle(asyncContext); + } +} +#else +const char *GetSmallString(const char *s) +{ + return s; +} + +int64_t SeekFile(int file, int64_t offset) +{ + return lseek(file, offset < 0 ? offset + 1 : offset, offset < 0 ? SEEK_END : SEEK_SET); +} + +template +int ReadFileToBuffer(int file, uint8_t *buf, size_t count, int &asyncContext, P asyncCancelProc) +{ + for (;;) { + int ret = static_cast(read(file, buf, count)); + if (ret >= 0 || !asyncContext || (errno != EAGAIN && errno != EWOULDBLOCK) || asyncCancelProc()) { + // Asynchronous FIFO reading can be opened before a writer opens it, then read() returns 0. + if (asyncContext && ret > 0) { + // Connected. + asyncContext = 2; + } + // For asynchronous, returns -1 for EOF + // For synchronous, 0 means EOF + return asyncContext == 2 && ret == 0 ? -1 : ret; + } + fd_set rfd; + FD_ZERO(&rfd); + FD_SET(file, &rfd); + timeval tv = {}; + tv.tv_sec = 1; + if (select(file + 1, &rfd, nullptr, nullptr, &tv) < 0) { + return -1; + } + } +} + +void CloseFile(int file, int asyncContext) +{ + static_cast(asyncContext); + if (file >= 0) { + close(file); + } +} +#endif +} + +#ifdef _WIN32 +int wmain(int argc, wchar_t **argv) +#else +int main(int argc, char **argv) +#endif +{ + int64_t seekOffset = 0; + int limitReadBytesPerSec = 0; + int timeoutSec = 0; + int timeoutMode = 0; + std::unordered_set excludePidSet; + std::unique_ptr traceFile(nullptr, fclose); + CServiceFilter servicefilter; + CTraceB24Caption traceb24; + CID3Converter id3conv; +#ifdef _WIN32 + const wchar_t *srcName = L""; + const wchar_t *traceName = L""; +#else + const char *srcName = ""; + const char *traceName = ""; +#endif + + for (int i = 1; i < argc; ++i) { + char c = '\0'; + const char *ss = GetSmallString(argv[i]); + if (ss[0] == '-' && ss[1] && !ss[2]) { + c = ss[1]; + } + if (c == 'h') { + fprintf(stderr, "Usage: tsreadex [-z ignored][-s seek][-l limit][-t timeout][-m mode][-x pids][-n prog_num_or_index][-a aud1][-b aud2][-c cap][-u sup][-r trace][-d flags] src\n"); + return 2; + } + bool invalid = false; + if (i < argc - 1) { + if (c == 'z') { + ++i; + } + else if (c == 's') { + seekOffset = strtoll(GetSmallString(argv[++i]), nullptr, 10); + } + else if (c == 'l') { + limitReadBytesPerSec = static_cast(strtol(GetSmallString(argv[++i]), nullptr, 10) * 1024); + invalid = !(0 <= limitReadBytesPerSec && limitReadBytesPerSec <= 32 * 1024 * 1024); + } + else if (c == 't') { + timeoutSec = static_cast(strtol(GetSmallString(argv[++i]), nullptr, 10)); + invalid = !(0 <= timeoutSec && timeoutSec <= 600); + } + else if (c == 'm') { + timeoutMode = static_cast(strtol(GetSmallString(argv[++i]), nullptr, 10)); + invalid = !(0 <= timeoutMode && timeoutMode <= 2); + } + else if (c == 'x') { + excludePidSet.clear(); + ++i; + for (size_t j = 0; argv[i][j];) { + ss = GetSmallString(argv[i] + j); + char *endp; + int pid = static_cast(strtol(ss, &endp, 10)); + excludePidSet.emplace(pid); + invalid = !(0 <= pid && pid <= 8191 && ss != endp && (!*endp || *endp == '/')); + if (invalid || !*endp) { + break; + } + j += endp - ss + 1; + } + } + else if (c == 'n') { + int n = static_cast(strtol(GetSmallString(argv[++i]), nullptr, 10)); + invalid = !(-256 <= n && n <= 65535); + servicefilter.SetProgramNumberOrIndex(n); + } + else if (c == 'a' || c == 'b' || c == 'c' || c == 'u') { + int mode = static_cast(strtol(GetSmallString(argv[++i]), nullptr, 10)); + if (c == 'a') { + invalid = !(0 <= mode && mode <= 13 && mode % 4 <= 1); + servicefilter.SetAudio1Mode(mode); + } + else if (c == 'b') { + invalid = !(0 <= mode && mode <= 7 && mode % 4 <= 3); + servicefilter.SetAudio2Mode(mode); + } + else { + invalid = !(0 <= mode && mode <= 6 && mode % 4 <= 2); + if (c == 'c') { + servicefilter.SetCaptionMode(mode); + } + else { + servicefilter.SetSuperimposeMode(mode); + } + } + } + else if (c == 'r') { + traceName = argv[++i]; + } + else if (c == 'd') { + id3conv.SetOption(static_cast(strtol(GetSmallString(argv[++i]), nullptr, 10))); + } + } + else { + srcName = argv[i]; + invalid = !srcName[0]; + } + if (invalid) { + fprintf(stderr, "Error: argument %d is invalid.\n", i); + return 1; + } + } + if (!srcName[0]) { + fprintf(stderr, "Error: not enough arguments.\n"); + return 1; + } + if (timeoutMode == 2) { + if (timeoutSec == 0) { + fprintf(stderr, "Error: timeout must not be 0 in non-blocking mode.\n"); + return 1; + } + if (seekOffset != 0) { + fprintf(stderr, "Error: cannot seek file in non-blocking mode.\n"); + } + } + +#ifdef _WIN32 + bool traceToStdout = traceName[0] == L'-' && !traceName[1]; + if (!traceToStdout && _setmode(_fileno(stdout), _O_BINARY) < 0) { + fprintf(stderr, "Error: _setmode.\n"); + return 1; + } + HANDLE asyncContext = nullptr; + if (timeoutMode == 2) { + asyncContext = CreateEvent(nullptr, TRUE, FALSE, nullptr); + if (!asyncContext) { + fprintf(stderr, "Error: unexpected.\n"); + return 1; + } + } + HANDLE file; + HANDLE openedFile = INVALID_HANDLE_VALUE; + if (srcName[0] == L'-' && !srcName[1]) { + file = GetStdHandle(STD_INPUT_HANDLE); + if (asyncContext) { + file = ReOpenFile(file, GENERIC_READ, FILE_SHARE_READ, FILE_FLAG_OVERLAPPED); + } + } + else { + file = CreateFileW(srcName, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN | (asyncContext ? FILE_FLAG_OVERLAPPED : 0), nullptr); + openedFile = file; + } + if (file == INVALID_HANDLE_VALUE) { + fprintf(stderr, "Error: cannot open file.\n"); + CloseFile(openedFile, asyncContext); + return 1; + } + if (!traceToStdout && traceName[0]) { + traceFile.reset(_wfopen(traceName, L"w")); + if (!traceFile) { + fprintf(stderr, "Warning: cannot open tracefile.\n"); + } + } +#else + bool traceToStdout = traceName[0] == '-' && !traceName[1]; + // 0: synchronous, 1: not connected yet, 2: connected. + int asyncContext = timeoutMode == 2; + int file; + int openedFile = -1; + if (srcName[0] == '-' && !srcName[1]) { + file = fileno(stdin); + if (asyncContext) { + if (fcntl(file, F_SETFL, O_NONBLOCK) == -1) { + file = -1; + } + } + } + else { + file = open(srcName, O_RDONLY | (asyncContext ? O_NONBLOCK : 0)); + openedFile = file; + } + if (file < 0) { + fprintf(stderr, "Error: cannot open file.\n"); + return 1; + } + if (!traceToStdout && traceName[0]) { + traceFile.reset(fopen(traceName, "w")); + if (!traceFile) { + fprintf(stderr, "Warning: cannot open tracefile.\n"); + } + } +#endif + traceb24.SetFile(traceToStdout ? stdout : traceFile.get()); + + int64_t filePos = 0; + if (seekOffset != 0) { + filePos = SeekFile(file, seekOffset); + if (filePos < 0) { + fprintf(stderr, "Error: seek failed.\n"); + CloseFile(openedFile, asyncContext); + return 1; + } + } + + static uint8_t buf[65536]; + int bufCount = 0; + int unitSize = 0; + size_t bufSize = sizeof(buf) / 8; + int measurementReadCount = 0; + auto lastWriteTime = std::chrono::steady_clock::now(); + auto lastMeasurementTime = lastWriteTime; + auto limitReadTime = lastWriteTime + std::chrono::seconds(1); + int64_t limitReadFilePos = filePos; + for (;;) { + // If timeoutMode == 1, read between "next to the syncword (buf[0])" and syncword. + size_t bufMax = unitSize == 0 ? bufSize : bufSize / unitSize * unitSize - (timeoutMode == 1 ? unitSize - 1 : 0); + int n = ReadFileToBuffer(file, buf + bufCount, bufMax - bufCount, asyncContext, [=]() { + return std::chrono::duration_cast(std::chrono::steady_clock::now() - lastWriteTime).count() >= timeoutSec; }); + bool retry = false; + bool completed = false; + int bufPos = -1; + if (timeoutMode == 0) { + // Synchronous, normal (may be appended) file/pipe + retry = n <= 0; + if (!retry) { + bufCount += n; + filePos += n; + } + } + else if (timeoutMode == 1) { + // Synchronous, may be preallocated file + if (n <= 0) { + filePos += bufCount - (unitSize == 0 ? 0 : 1); + timeoutMode = 0; + } + else { + bufCount += n; + if (bufCount == static_cast(bufMax)) { + if (unitSize == 0) { + bufPos = resync_ts(buf, bufCount, &unitSize); + if (unitSize == 0) { + retry = true; + bufCount = 0; + bufPos = -1; + } + else { + // Keep bufPos always 0 + filePos += bufPos + 1; + buf[0] = buf[bufPos]; + bufCount = 1; + bufPos = 0; + if (SeekFile(file, filePos) != filePos) { + fprintf(stderr, "Warning: seek failed.\n"); + completed = true; + } + } + } + else { + // Keep bufPos always 0 + bufPos = resync_ts(buf, bufCount, &unitSize); + if (bufPos != 0) { + retry = true; + bufCount = 1; + bufPos = 0; + } + else { + filePos += bufCount - 1; + } + } + } + } + } + else { + // Asynchronous, pipe + if (n < 0) { + completed = true; + } +#ifndef _WIN32 + else if (n <= 0) { + retry = true; + } +#endif + else { + bufCount += n; + filePos += n; + } + } + + if (retry) { + if (timeoutSec == 0 || + std::chrono::duration_cast(std::chrono::steady_clock::now() - lastWriteTime).count() >= timeoutSec) { + completed = true; + } + else { + SleepFor(std::chrono::milliseconds(200)); + if (timeoutMode != 2 && SeekFile(file, filePos) != filePos) { + fprintf(stderr, "Warning: seek failed.\n"); + completed = true; + } + } + } + + if (bufCount == static_cast(bufMax) || completed) { + if (bufPos < 0) { + bufPos = resync_ts(buf, bufCount, &unitSize); + } + for (int i = bufPos; unitSize != 0 && i + unitSize <= bufCount; i += unitSize) { + if (excludePidSet.count(extract_ts_header_pid(buf + i)) == 0) { + servicefilter.AddPacket(buf + i); + } + } + for (auto it = servicefilter.GetPackets().cbegin(); it != servicefilter.GetPackets().end(); it += 188) { + traceb24.AddPacket(&*it); + id3conv.AddPacket(&*it); + } + servicefilter.ClearPackets(); + + auto nowTime = std::chrono::steady_clock::now(); + if (++measurementReadCount >= 500) { + // Maximize buffer size + bufSize = sizeof(buf); + } + if (std::chrono::duration_cast(nowTime - lastMeasurementTime).count() >= 1) { + // Decrease/Increase buffer size + bufSize = measurementReadCount < 10 ? std::max(bufSize - sizeof(buf) / 8, sizeof(buf) / 8) : + std::min(bufSize + sizeof(buf) / 8, sizeof(buf)); + measurementReadCount = 0; + lastMeasurementTime = nowTime; + } + if (!id3conv.GetPackets().empty()) { + if (!traceToStdout) { + if (fwrite(id3conv.GetPackets().data(), 1, id3conv.GetPackets().size(), stdout) != id3conv.GetPackets().size()) { + completed = true; + } + } + id3conv.ClearPackets(); + lastWriteTime = std::chrono::steady_clock::now(); + } + else if (timeoutSec != 0 && + std::chrono::duration_cast(nowTime - lastWriteTime).count() >= timeoutSec) { + completed = true; + } + if (completed) { + break; + } + if (unitSize == 0) { + bufCount = 0; + } + else { + if ((bufPos != 0 || bufCount >= unitSize) && (bufCount - bufPos) % unitSize != 0) { + std::copy(buf + bufPos + (bufCount - bufPos) / unitSize * unitSize, buf + bufCount, buf); + } + bufCount = (bufCount - bufPos) % unitSize; + } + } + + if (limitReadBytesPerSec != 0) { + if (filePos - limitReadFilePos > limitReadBytesPerSec) { + // Too fast + auto nowTime = std::chrono::steady_clock::now(); + if (limitReadTime > nowTime) { + SleepFor(std::chrono::duration_cast(limitReadTime - nowTime)); + } + } + auto nowTime = std::chrono::steady_clock::now(); + if (nowTime >= limitReadTime) { + limitReadTime = nowTime + std::chrono::seconds(1); + limitReadFilePos = filePos; + } + } + } + + CloseFile(openedFile, asyncContext); + return 0; +} diff --git a/tsreadex/util.cpp b/tsreadex/util.cpp new file mode 100644 index 0000000..55e9fb2 --- /dev/null +++ b/tsreadex/util.cpp @@ -0,0 +1,157 @@ +#include "util.hpp" +#include + +uint16_t calc_crc16_ccitt(const uint8_t *data, int data_size, uint16_t crc) +{ + for (int i = 0; i < data_size; ++i) { + uint16_t c = ((crc >> 8) ^ data[i]) << 8; + for (int j = 0; j < 8; ++j) { + c = (c << 1) ^ (c & 0x8000 ? 0x1021 : 0); + } + crc = (crc << 8) ^ c; + } + return crc; +} + +uint32_t calc_crc32(const uint8_t *data, int data_size, uint32_t crc) +{ + for (int i = 0; i < data_size; ++i) { + uint32_t c = ((crc >> 24) ^ data[i]) << 24; + for (int j = 0; j < 8; ++j) { + c = (c << 1) ^ (c & 0x80000000 ? 0x04c11db7 : 0); + } + crc = (crc << 8) ^ c; + } + return crc; +} + +int extract_psi(PSI *psi, const uint8_t *payload, int payload_size, int unit_start, int counter) +{ + int copy_pos = 0; + int copy_size = payload_size; + int done = 1; + if (unit_start) { + if (payload_size < 1) { + psi->continuity_counter = psi->data_count = psi->version_number = 0; + return 1; + } + int pointer = payload[0]; + psi->continuity_counter = (psi->continuity_counter + 1) & 0x2f; + if (pointer > 0 && psi->continuity_counter == (0x20 | counter)) { + copy_pos = 1; + copy_size = pointer; + // Call the function again + done = 0; + } + else { + psi->continuity_counter = 0x20 | counter; + psi->data_count = psi->version_number = 0; + copy_pos = 1 + pointer; + copy_size -= copy_pos; + } + } + else { + psi->continuity_counter = (psi->continuity_counter + 1) & 0x2f; + if (psi->continuity_counter != (0x20 | counter)) { + psi->continuity_counter = psi->data_count = psi->version_number = 0; + return 1; + } + } + if (copy_size > 0 && copy_pos + copy_size <= payload_size) { + copy_size = std::min(copy_size, static_cast(sizeof(psi->data)) - psi->data_count); + std::copy(payload + copy_pos, payload + copy_pos + copy_size, psi->data + psi->data_count); + psi->data_count += copy_size; + } + + // If psi->version_number != 0, these fields are valid. + if (psi->data_count >= 3) { + int section_length = ((psi->data[1] & 0x03) << 8) | psi->data[2]; + if (psi->data_count >= 3 + section_length && + calc_crc32(psi->data, 3 + section_length) == 0 && + section_length >= 3) + { + psi->table_id = psi->data[0]; + psi->section_length = section_length; + psi->version_number = 0x20 | ((psi->data[5] >> 1) & 0x1f); + psi->current_next_indicator = psi->data[5] & 0x01; + } + } + return done; +} + +void extract_pat(PAT *pat, const uint8_t *payload, int payload_size, int unit_start, int counter) +{ + int done; + do { + done = extract_psi(&pat->psi, payload, payload_size, unit_start, counter); + if (pat->psi.version_number && + pat->psi.current_next_indicator && + pat->psi.table_id == 0 && + pat->psi.section_length >= 5) + { + // Update PAT + const uint8_t *table = pat->psi.data; + pat->transport_stream_id = (table[3] << 8) | table[4]; + pat->version_number = pat->psi.version_number; + + // Update PMT list + pat->pmt.clear(); + int pos = 3 + 5; + while (pos + 3 < 3 + pat->psi.section_length - 4/*CRC32*/) { + // Including NIT (program_number == 0) + pat->pmt.resize(pat->pmt.size() + 1); + pat->pmt.back().pmt_pid = ((table[pos + 2] & 0x1f) << 8) | table[pos + 3]; + pat->pmt.back().program_number = (table[pos] << 8) | (table[pos + 1]); + pos += 4; + } + } + } + while (!done); +} + +int get_ts_payload_size(const uint8_t *packet) +{ + int adaptation = extract_ts_header_adaptation(packet); + if (adaptation & 1) { + if (adaptation == 3) { + int adaptation_length = packet[4]; + if (adaptation_length <= 183) { + return 183 - adaptation_length; + } + } + else { + return 184; + } + } + return 0; +} + +int resync_ts(const uint8_t *data, int data_size, int *unit_size) +{ + if (*unit_size == 188 || *unit_size == 192 || *unit_size == 204) { + for (int offset = 0; offset < data_size && offset < *unit_size; ++offset) { + int i = offset; + for (; i < data_size; i += *unit_size) { + if (data[i] != 0x47) { + break; + } + } + if (i >= data_size) { + return offset; + } + } + } + else { + // Unknown unit size + for (int i = 0; i < 3; ++i) { + *unit_size = i == 0 ? 188 : i == 1 ? 192 : 204; + int offset = resync_ts(data, data_size, unit_size); + if (offset < data_size) { + return offset; + } + } + *unit_size = 0; + } + // Failed + return data_size; +} diff --git a/tsreadex/util.hpp b/tsreadex/util.hpp new file mode 100644 index 0000000..ef27192 --- /dev/null +++ b/tsreadex/util.hpp @@ -0,0 +1,64 @@ +#ifndef INCLUDE_UTIL_HPP +#define INCLUDE_UTIL_HPP + +#include +#include +#include + +struct PSI +{ + int table_id; + int section_length; + int version_number; + int current_next_indicator; + int continuity_counter; + int data_count; + uint8_t data[1024]; +}; + +struct PMT_REF +{ + int pmt_pid; + int program_number; +}; + +struct PAT +{ + int transport_stream_id; + int version_number; + std::vector pmt; + PSI psi; +}; + +uint16_t calc_crc16_ccitt(const uint8_t *data, int data_size, uint16_t crc = 0); +uint32_t calc_crc32(const uint8_t *data, int data_size, uint32_t crc = 0xffffffff); +int extract_psi(PSI *psi, const uint8_t *payload, int payload_size, int unit_start, int counter); +void extract_pat(PAT *pat, const uint8_t *payload, int payload_size, int unit_start, int counter); +int get_ts_payload_size(const uint8_t *packet); +int resync_ts(const uint8_t *data, int data_size, int *unit_size); + +inline int extract_ts_header_unit_start(const uint8_t *packet) { return !!(packet[1] & 0x40); } +inline int extract_ts_header_pid(const uint8_t *packet) { return ((packet[1] & 0x1f) << 8) | packet[2]; } +inline int extract_ts_header_adaptation(const uint8_t *packet) { return (packet[3] >> 4) & 0x03; } +inline int extract_ts_header_counter(const uint8_t *packet) { return packet[3] & 0x0f; } + +inline uint8_t extract_bit(const uint8_t *data, size_t pos) +{ + return (data[pos >> 3] >> (7 - (pos & 7))) & 1; +} + +inline bool read_bool(const uint8_t *data, size_t &pos) +{ + return !!extract_bit(data, pos++); +} + +inline int read_bits(const uint8_t *data, size_t &pos, int n) +{ + int r = 0; + while (--n >= 0) { + r |= extract_bit(data, pos++) << n; + } + return r; +} + +#endif diff --git a/tsreadex_wrapper.cpp b/tsreadex_wrapper.cpp new file mode 100644 index 0000000..f893fed --- /dev/null +++ b/tsreadex_wrapper.cpp @@ -0,0 +1,165 @@ +#include "tsreadex_wrapper.h" +#include "tsreadex/servicefilter.hpp" +#include "tsreadex/util.hpp" +#include + +namespace tardsplaya { + +TSReadEXProcessor::TSReadEXProcessor() + : enabled_(false), pid_filter_table_(8192, false) { +} + +TSReadEXProcessor::~TSReadEXProcessor() = default; + +bool TSReadEXProcessor::Initialize(const Config& config) { + config_ = config; + enabled_ = config.enabled; + + if (!enabled_) { + return true; // Success, but not enabled + } + + try { + // Create TSReadEX service filter + filter_ = std::make_unique(); + + // Configure service selection + filter_->SetProgramNumberOrIndex(config.program_number); + + // Configure audio options + int audio1_mode = 0; + int audio2_mode = 0; + + if (config.complement_missing_audio) { + audio1_mode |= 1; // Add silent audio if missing + audio2_mode |= 1; + } + + if (config.ensure_stereo) { + audio1_mode |= 4; // Convert mono to stereo + audio2_mode |= 4; + } + + filter_->SetAudio1Mode(audio1_mode); + filter_->SetAudio2Mode(audio2_mode); + + // Configure caption/superimpose (typically not used for Twitch) + filter_->SetCaptionMode(2); // Remove captions (not applicable to Twitch) + filter_->SetSuperimposeMode(2); // Remove superimpose (not applicable to Twitch) + + // Setup PID filtering + SetupPIDFiltering(); + + // Reset statistics + stats_ = Stats{}; + + return true; + } catch (const std::exception&) { + enabled_ = false; + filter_.reset(); + return false; + } +} + +void TSReadEXProcessor::SetupPIDFiltering() { + // Reset filter table + std::fill(pid_filter_table_.begin(), pid_filter_table_.end(), false); + + // Add excluded PIDs to filter table + for (int pid : config_.exclude_pids) { + if (pid >= 0 && pid < 8192) { + pid_filter_table_[pid] = true; + } + } + + // Standard PIDs to exclude for stream cleaning + if (config_.remove_eit) { + // EIT (Event Information Table) PIDs - program guide data + pid_filter_table_[0x12] = true; // EIT actual/other + pid_filter_table_[0x26] = true; // EIT schedule actual/other + pid_filter_table_[0x27] = true; // EIT schedule actual/other + + // Other non-essential PIDs for streaming + pid_filter_table_[0x14] = true; // TDT/TOT (Time and Date Table) + pid_filter_table_[0x70] = true; // DIT (Discontinuity Information Table) + pid_filter_table_[0x71] = true; // SIT (Selection Information Table) + } +} + +bool TSReadEXProcessor::IsPacketFiltered(const uint8_t* packet) const { + if (!packet || packet[0] != 0x47) { + return true; // Filter invalid packets + } + + // Extract PID from TS header + int pid = ((packet[1] & 0x1F) << 8) | packet[2]; + + return pid_filter_table_[pid]; +} + +bool TSReadEXProcessor::ProcessChunk(const std::vector& input, std::vector& output) { + output.clear(); + + if (!IsEnabled()) { + // Pass through without processing + output = input; + return true; + } + + stats_.bytes_input += input.size(); + + try { + // Clear previous output from filter + filter_->ClearPackets(); + + // Process input in 188-byte TS packet chunks + const size_t packet_size = 188; + size_t pos = 0; + + while (pos + packet_size <= input.size()) { + const uint8_t* packet = input.data() + pos; + + // Verify sync byte + if (packet[0] != 0x47) { + // Try to find next sync byte + while (pos < input.size() && input[pos] != 0x47) { + pos++; + } + continue; + } + + stats_.packets_processed++; + + // Check if packet should be filtered out + if (IsPacketFiltered(packet)) { + stats_.packets_filtered++; + pos += packet_size; + continue; + } + + // Process packet through TSReadEX service filter + filter_->AddPacket(packet); + + pos += packet_size; + } + + // Get processed packets from filter + const auto& filtered_packets = filter_->GetPackets(); + output.insert(output.end(), filtered_packets.begin(), filtered_packets.end()); + + stats_.bytes_output += output.size(); + + return true; + } catch (const std::exception&) { + return false; + } +} + +void TSReadEXProcessor::Reset() { + if (filter_) { + filter_->ClearPackets(); + } + // Note: Don't reset stats, they accumulate across stream lifetime +} + +} // namespace tardsplaya \ No newline at end of file diff --git a/tsreadex_wrapper.h b/tsreadex_wrapper.h new file mode 100644 index 0000000..e646e09 --- /dev/null +++ b/tsreadex_wrapper.h @@ -0,0 +1,87 @@ +#pragma once +#include +#include +#include + +// Forward declaration of TSReadEX classes +class CServiceFilter; + +namespace tardsplaya { + +/** + * TSReadEX Integration for Tardsplaya + * + * Provides optional MPEG-TS stream filtering and enhancement capabilities + * using the TSReadEX library for improved stream compatibility. + */ +class TSReadEXProcessor { +public: + struct Config { + bool enabled = false; // Enable TSReadEX processing + int program_number = -1; // Target program number (-1 for first program) + bool remove_eit = true; // Remove EIT (program guide) packets + bool stabilize_audio = true; // Ensure consistent audio streams + bool standardize_pids = true; // Remap PIDs to standard values + std::vector exclude_pids; // PIDs to exclude from output + + // Audio enhancement options + bool ensure_stereo = false; // Convert mono audio to stereo + bool complement_missing_audio = true; // Add silent audio if missing + + // Advanced options + int timeout_seconds = 0; // Processing timeout (0 = no timeout) + int rate_limit_kbps = 0; // Rate limiting (0 = unlimited) + }; + + TSReadEXProcessor(); + ~TSReadEXProcessor(); + + /** + * Initialize the processor with the given configuration + */ + bool Initialize(const Config& config); + + /** + * Process a chunk of MPEG-TS data + * @param input Input TS packets + * @param output Filtered/processed TS packets + * @return true if processing succeeded + */ + bool ProcessChunk(const std::vector& input, std::vector& output); + + /** + * Check if TSReadEX processing is enabled and initialized + */ + bool IsEnabled() const { return enabled_ && filter_ != nullptr; } + + /** + * Get processing statistics + */ + struct Stats { + size_t packets_processed = 0; + size_t packets_filtered = 0; + size_t bytes_input = 0; + size_t bytes_output = 0; + }; + + Stats GetStats() const { return stats_; } + + /** + * Reset processor state (call between streams) + */ + void Reset(); + +private: + bool enabled_; + Config config_; + std::unique_ptr filter_; + Stats stats_; + + // PID filtering state + std::vector pid_filter_table_; // 8192 entries for all possible PIDs + + void SetupPIDFiltering(); + bool IsPacketFiltered(const uint8_t* packet) const; +}; + +} // namespace tardsplaya \ No newline at end of file