Skip to content

fix(mod_shout): update handle->pos in shout_file_read() to fix backward seek for MP3 files#2996

Open
pinc444 wants to merge 11 commits intosignalwire:masterfrom
pinc444:master
Open

fix(mod_shout): update handle->pos in shout_file_read() to fix backward seek for MP3 files#2996
pinc444 wants to merge 11 commits intosignalwire:masterfrom
pinc444:master

Conversation

@pinc444
Copy link
Copy Markdown

@pinc444 pinc444 commented Mar 24, 2026

mod_shout's shout_file_read() never updated handle->pos, causing switch_core_file_seek() to always compute 0 - samps for backward seeks — clamping to position 0 instead of the correct relative position.

Change

Added handle->pos += *len in shout_file_read(), matching the pattern already used by mod_sndfile:

// src/mod/formats/mod_shout/mod_shout.c — shout_file_read()
handle->pos += *len;          // was missing; mod_sndfile has this
handle->sample_count += *len;

This ensures fh->pos accurately reflects playback position so that callers doing fh->pos - samps (backward seek via SEEK_SET) compute the correct target sample.

Original prompt

Bug

When using session.streamFile() with an MP3 file (handled by mod_shout), backward seek does not work correctly. The file always seeks to position 0 instead of the expected relative backward position.

WAV files work correctly because mod_sndfile properly tracks handle->pos in its file_read() function. mod_shout does not update handle->pos during reads, which breaks the seek logic in switch_core_file_seek().

Root Cause

In src/mod/formats/mod_shout/mod_shout.c, the function shout_file_read() increments handle->sample_count but never updates handle->pos.

Meanwhile, in src/switch_core_file.c, the switch_core_file_seek() function uses fh->offset_pos for SEEK_CUR operations:

if (whence == SWITCH_SEEK_CUR) {
    unsigned int cur = 0;
    if (switch_test_flag(fh, SWITCH_FILE_FLAG_WRITE)) {
        fh->file_interface->file_seek(fh, &cur, fh->samples_out, SEEK_SET);
    } else {
        fh->file_interface->file_seek(fh, &cur, fh->offset_pos, SEEK_SET);
    }
}

And fh->offset_pos is set from *cur_pos which comes from the file_seek return. But the first seek (to establish current position) relies on fh->offset_pos being correct. Since mod_shout never updates handle->pos (and offset_pos is derived from it after seeks), the position is always 0, making backward seeks always go to position 0.

For comparison, mod_sndfile in src/mod/formats/mod_sndfile/mod_sndfile.c correctly updates handle->pos in its read function:

static switch_status_t sndfile_file_read(switch_file_handle_t *handle, void *data, size_t *len)
{
    // ... read logic ...
    handle->pos += *len;        // <-- THIS IS WHAT mod_shout IS MISSING
    handle->sample_count += *len;
    return *len ? SWITCH_STATUS_SUCCESS : SWITCH_STATUS_FALSE;
}

Fix Required

In src/mod/formats/mod_shout/mod_shout.c, find the shout_file_read() function. It currently has a line:

handle->sample_count += *len;

Add the following line immediately before that existing line:

handle->pos += *len;

So the result looks like:

handle->pos += *len;
handle->sample_count += *len;

This is the only change needed. It matches the pattern used by mod_sndfile and ensures that switch_core_file_seek() has the correct current position when computing relative seeks (both forward and backward via SEEK_CUR).

The following is the prior conversation context from the user's chat exploration (may be truncated):

Assistant:

# Generates a 2-minute WAV file with a voice announcing each second
# Uses .NET System.Speech (built into Windows)

Add-Type -AssemblyName System.Speech

$SAMPLE_RATE = 8000
$OUTPUT = Join-Path (Get-Location) "counting_seconds.wav"
$TEMP_DIR = Join-Path $env:TEMP "counting_$(Get-Random)"
New-Item -ItemType Directory -Path $TEMP_DIR -Force | Out-Null

$TOTAL_SECONDS = 120

Write-Host "Generating $TOTAL_SECONDS seconds of spoken counting audio..."
Write-Host ""

$synth = New-Object System.Speech.Synthesis.SpeechSynthesizer

for ($i = 0; $i -le $TOTAL_SECONDS; $i++) {
    $voiceFile = Join-Path $TEMP_DIR "voice_$($i.ToString('D4')).wav"
    $finalFile = Join-Path $TEMP_DIR "final_$($i.ToString('D4')).wav"

    # Generate spoken number to WAV
    $synth.SetOutputToWaveFile($voiceFile)
    $synth.Rate = 3  # faster speech so it fits in 1 second
    $synth.Speak("$i")
    $synth.SetOutputToNull()

    # Convert to 8kHz mono and pad/trim to exactly 1 second
    $soxArgs = @(
        "`"$voiceFile`""
        "-r", "$SAMPLE_RATE"
        "-c", "1"
        "`"$finalFile`""
        "pad", "0", "1.0"
        "trim", "0", "1.0"
    )
    Start-Process -FilePath "sox" -ArgumentList ($soxArgs -join " ") -NoNewWindow -Wait

    Write-Host "`r  Generated second $i / $TOTAL_SECONDS" -NoNewline
}

$synth.Dispose()

Write-Host ""
Write-Host "Combining all seconds into $OUTPUT..."

# Get all final files in order
$allFiles = (0..$TOTAL_SECONDS | ForEach-Object {
    "`"$(Join-Path $TEMP_DIR "final_$($_.ToString('D4')).wav")`""
}) -join " "

Start-Process -FilePath "sox" -ArgumentList "$allFiles `"$OUTPUT`"" -NoNewWindow -Wait

Write-Host ""
Write-Host "=========================================="
Write-Host "  FILE GENERATED: $OUTPUT"
Write-Host "=========================================="
Write-Host ""
Write-Host "  You will hear: 'zero' 'one' 'two' ... 'one hundred twenty'"
Write-Host "  Each number takes exactly 1 second."
Write-Host "  The number you hear = the second position in the file."
Write-Host ""
Write-Host "HOW TO TEST:"
Write-Host ""
Write-Host "  1. Play this file with your streamFile + onPlayFile callback"
Write-Host "  2. Wait until you hear 'sixty'"
Write-Host "  3. Press BACKWARD key (1) - seek:-15000"
Write-Host "     Should hear: 'forty-five' (60 - 15 = 45)"
Write-Host "     Bug: you hear 'zero' or 'one'"
Write-Host ""
Write-Host "  4. Reset. Wait until you hear 'sixty' again"
Write-Host ...

</details>

Copilot AI and others added 11 commits February 20, 2026 03:55
Co-authored-by: pinc444 <148037078+pinc444@users.noreply.github.com>
Add mod_soundtouch: pitch-preserving tempo control for file playback
… MP3 files

Co-authored-by: pinc444 <148037078+pinc444@users.noreply.github.com>
fix(mod_shout): update handle->pos in shout_file_read() to fix backward seek for MP3 files
Add handle->pos += *len in shout_file_read() to keep the file position
counter in sync after backward seeks. Without this, mpg123_seek()
correctly repositions the decoder but subsequent reads never update
handle->pos, causing the position to drift and breaking backward
seek behavior.

This matches the pattern used by other format modules (mod_sndfile,
mod_opusfile, mod_native_file) which all update handle->pos in their
read functions.

Co-authored-by: pinc444 <148037078+pinc444@users.noreply.github.com>
Agent-Logs-Url: https://github.com/pinc444/freeswitch/sessions/3063de57-1880-4d2e-b422-c6dea22f6b7f
fix(mod_shout): update handle->pos in shout_file_read() to fix backward seek for MP3 files
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants