[FFmpeg-devel] Mono ADPCM for EA WVE Files / Fix Framerate

Peter Ross pross at xvid.org
Sat Jul 20 02:54:37 EEST 2024


On Fri, Jul 19, 2024 at 07:34:18AM -0400, redacted redacted wrote:
> Hello there,
> 
> The Sims 1: Unleashed makes use of WVE files for its intro videos.
> Two of the files for the game use Mono ADPCM audio instead of Stereo.
> However, FFmpeg's ADPCM_EA codec always expects the files to be in Stereo.

can you post a sample file somewhere

> In addition, they're supposed to play at 30 fps, but the EA demuxer assumes
> 15 by default.
> It appears the framerate is set with the use of the 0x1B code in the SCHl /
> PT00 header.

nice find

> I have made changes in the patch attached to fix these problems.

thx for sharing.
the patch needs a small amount of improvement. see below for my suggestions.

> From 306f9db010cf000eb8477aca243fc970f5b95df8 Mon Sep 17 00:00:00 2001
> From: aaron <aaronrules5alt at gmail.com>
> Date: Fri, 19 Jul 2024 07:30:10 -0400
> Subject: [PATCH 1/1] Mono ADPCM for EA WVE Files / Fix Framerate
> 
> ---
>  libavcodec/adpcm.c           | 57 +++++++++++++++++++++++++-----------
>  libavformat/electronicarts.c | 12 ++++++--
>  2 files changed, 50 insertions(+), 19 deletions(-)
> 
> diff --git a/libavcodec/adpcm.c b/libavcodec/adpcm.c
> index f63afefd63..238214877d 100644
> --- a/libavcodec/adpcm.c
> +++ b/libavcodec/adpcm.c
> @@ -262,7 +262,7 @@ static av_cold int adpcm_decode_init(AVCodecContext * avctx)
>          break;
>      case AV_CODEC_ID_ADPCM_DTK:
>      case AV_CODEC_ID_ADPCM_EA:
> -        min_channels = 2;
> +        min_channels = 1;
>          break;
>      case AV_CODEC_ID_ADPCM_AFC:
>      case AV_CODEC_ID_ADPCM_EA_R1:
> @@ -914,10 +914,12 @@ static int get_nb_samples(AVCodecContext *avctx, GetByteContext *gb,
>          bytestream2_seek(gb, -8, SEEK_CUR);
>          break;
>      case AV_CODEC_ID_ADPCM_EA:
> +        /* Stereo is 30 bytes per block */
> +        /* Mono is 15 bytes per block */
>          has_coded_samples = 1;
>          *coded_samples  = bytestream2_get_le32(gb);
>          *coded_samples -= *coded_samples % 28;
> -        nb_samples      = (buf_size - 12) / 30 * 28;
> +        nb_samples      = (buf_size - 12) / (ch == 2 ? 30 : 15) * 28;
>          break;
>      case AV_CODEC_ID_ADPCM_IMA_EA_EACS:
>          has_coded_samples = 1;
> @@ -1652,10 +1654,10 @@ static int adpcm_decode_frame(AVCodecContext *avctx, AVFrame *frame,
>          int coeff1l, coeff2l, coeff1r, coeff2r;
>          int shift_left, shift_right;
>  
> -        /* Each EA ADPCM frame has a 12-byte header followed by 30-byte pieces,
> -           each coding 28 stereo samples. */
> -
> -        if (channels != 2)
> +        /* Each EA ADPCM frame has a 12-byte header followed by 30-byte (stereo) or 15-byte (mono) pieces,
> +           each coding 28 stereo/mono samples. */
> +        
> +        if (channels != 2 && channels != 1)
>              return AVERROR_INVALIDDATA;
>  
>          current_left_sample   = sign_extend(bytestream2_get_le16u(&gb), 16);
> @@ -1665,37 +1667,58 @@ static int adpcm_decode_frame(AVCodecContext *avctx, AVFrame *frame,
>  
>          for (int count1 = 0; count1 < nb_samples / 28; count1++) {
>              int byte = bytestream2_get_byteu(&gb);
> -            coeff1l = ea_adpcm_table[ byte >> 4       ];
> -            coeff2l = ea_adpcm_table[(byte >> 4  ) + 4];
> +            coeff1l = ea_adpcm_table[ byte >> 4 ];
> +            coeff2l = ea_adpcm_table[(byte >> 4) + 4];

these whitespace-only changes shouldn't go in the patch.

>              coeff1r = ea_adpcm_table[ byte & 0x0F];
>              coeff2r = ea_adpcm_table[(byte & 0x0F) + 4];
>  
> -            byte = bytestream2_get_byteu(&gb);
> -            shift_left  = 20 - (byte >> 4);
> -            shift_right = 20 - (byte & 0x0F);
> -
> -            for (int count2 = 0; count2 < 28; count2++) {
> +            if (channels == 2){
> +                byte = bytestream2_get_byteu(&gb);
> +                shift_left = 20 - (byte >> 4);
> +                shift_right = 20 - (byte & 0x0F);
> +            } else{
> +                /* Mono packs the shift into the coefficient byte's lower nibble instead */
> +                shift_left = 20 - (byte & 0x0F);
> +            }
> +            
> +            for (int count2 = 0; count2 < ( channels == 2 ? 28 : 14); count2++) {

"count2 < ( channels..." looks out of place.
drop the space after the parenthesis.

>                  byte = bytestream2_get_byteu(&gb);
>                  next_left_sample  = sign_extend(byte >> 4, 4) * (1 << shift_left);
> -                next_right_sample = sign_extend(byte,      4) * (1 << shift_right);
> +                next_right_sample = sign_extend(byte, 4) * (1 << shift_right);
>  
>                  next_left_sample = (next_left_sample +
>                      (current_left_sample * coeff1l) +
>                      (previous_left_sample * coeff2l) + 0x80) >> 8;
> +
>                  next_right_sample = (next_right_sample +
>                      (current_right_sample * coeff1r) +
>                      (previous_right_sample * coeff2r) + 0x80) >> 8;
>  
>                  previous_left_sample = current_left_sample;
>                  current_left_sample = av_clip_int16(next_left_sample);
> +
>                  previous_right_sample = current_right_sample;
>                  current_right_sample = av_clip_int16(next_right_sample);
> +
>                  *samples++ = current_left_sample;
> -                *samples++ = current_right_sample;
> +

ditto for these whitespace-only changes above.

> +                if (channels == 2){
> +                    *samples++ = current_right_sample;
> +                } else {
> +                    next_left_sample  = sign_extend(byte, 4) * (1 << shift_left);
> +
> +                    next_left_sample = (next_left_sample +
> +                        (current_left_sample * coeff1l) +
> +                        (previous_left_sample * coeff2l) + 0x80) >> 8;
> +
> +                    previous_left_sample = current_left_sample;
> +                    current_left_sample = av_clip_int16(next_left_sample);
> +
> +                    *samples++ = current_left_sample;
> +                }
>              }
>          }
> -
> -        bytestream2_skip(&gb, 2); // Skip terminating 0x0000
> +        bytestream2_skip(&gb, channels == 2 ? 2 : 3); // Skip terminating NULs
>          ) /* End of CASE */
>      CASE(ADPCM_EA_MAXIS_XA,
>          int coeff[2][2], shift[2];

i suggest splitting this into two patches, one for mono adpcm ea, another for
the frame rate fix.

> diff --git a/libavformat/electronicarts.c b/libavformat/electronicarts.c
> index f7f6fd4cab..c141a172dd 100644
> --- a/libavformat/electronicarts.c
> +++ b/libavformat/electronicarts.c
> @@ -86,6 +86,8 @@ typedef struct EaDemuxContext {
>      enum AVCodecID audio_codec;
>      int audio_stream_index;
>  
> +    int framerate;
> +
>      int bytes;
>      int sample_rate;
>      int num_channels;
> @@ -198,6 +200,10 @@ static int process_audio_header_elements(AVFormatContext *s)
>              av_log(s, AV_LOG_DEBUG, "end of header block reached\n");
>              in_header = 0;
>              break;
> +        case 0x1B:
> +            ea->framerate = read_arbitrary(pb);
> +            av_log(s, AV_LOG_DEBUG, "Setting framerate to %u", ea->framerate);
> +            break;

av_log trailing "\n" missing

>          default:
>              av_log(s, AV_LOG_DEBUG,
>                     "header element 0x%02x set to 0x%08"PRIx32"\n",
> @@ -367,6 +373,8 @@ static int process_ea_header(AVFormatContext *s)
>      AVIOContext *pb    = s->pb;
>      int i;
>  
> +    ea->framerate = 15;
> +
>      for (i = 0; i < 5 && (!ea->audio_codec || !ea->video.codec); i++) {
>          uint64_t startpos     = avio_tell(pb);
>          int err               = 0;
> @@ -427,12 +435,12 @@ static int process_ea_header(AVFormatContext *s)
>          case pQGT_TAG:
>          case TGQs_TAG:
>              ea->video.codec = AV_CODEC_ID_TGQ;
> -            ea->video.time_base   = (AVRational) { 1, 15 };
> +            ea->video.time_base   = (AVRational) { 1, ea->framerate };
>              break;
>  
>          case pIQT_TAG:
>              ea->video.codec = AV_CODEC_ID_TQI;
> -            ea->video.time_base   = (AVRational) { 1, 15 };
> +            ea->video.time_base   = (AVRational) { 1, ea->framerate };
>              break;
>  
>          case MADk_TAG:
> -- 

-- Peter
(A907 E02F A6E5 0CD2 34CD 20D2 6760 79C5 AC40 DD6B)
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 195 bytes
Desc: not available
URL: <https://ffmpeg.org/pipermail/ffmpeg-devel/attachments/20240720/7c635930/attachment.sig>


More information about the ffmpeg-devel mailing list