Browse Source

avi support

Wei Mingzhi 6 years ago
parent
commit
af4b5b3a71
11 changed files with 919 additions and 1 deletions
  1. 6 0
      audio.c
  2. 764 0
      aviplay.c
  3. 40 0
      aviplay.h
  4. 25 0
      ending.c
  5. 6 0
      macos/Pal.xcodeproj/project.pbxproj
  6. 17 0
      main.c
  7. 1 0
      main.h
  8. 5 0
      uigame.c
  9. 49 0
      video.c
  10. 5 0
      video.h
  11. 1 1
      yj1.c

+ 6 - 0
audio.c

@@ -28,6 +28,7 @@
 #include "util.h"
 #include "resampler.h"
 #include "midi.h"
+#include "aviplay.h"
 #include <math.h>
 
 #if PAL_HAS_OGG
@@ -167,6 +168,11 @@ AUDIO_FillBuffer(
 	   AUDIO_MixNative((short *)stream, gAudioDevice.pSoundBuffer, len >> 1);
    }
 
+   //
+   // Play sound for AVI
+   //
+   AVI_FillAudioBuffer(NULL, (LPBYTE)stream, len);
+
    //
    // Convert audio from native byte-order to actual byte-order
    //

+ 764 - 0
aviplay.c

@@ -0,0 +1,764 @@
+/* -*- mode: c; tab-width: 4; c-basic-offset: 4; c-file-style: "linux" -*- */
+//
+// Copyright (c) 2017, SDLPAL development team.
+// All rights reserved.
+//
+// This file is part of SDLPAL.
+//
+// SDLPAL is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+
+//
+// aviplay.c
+//
+// Simple quick and dirty AVI player specially designed for PAL Win95.
+//
+
+/*
+ * Portions based on:
+ *
+ * Microsoft Video-1 Decoder
+ * Copyright (C) 2003 The FFmpeg project
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Microsoft Video-1 Decoder by Mike Melanson (melanson@pcisys.net)
+ * For more information about the MS Video-1 format, visit:
+ *   http://www.pcisys.net/~melanson/codecs/
+ */
+
+#include "main.h"
+
+typedef struct
+{
+    FILE         *fp;                 // pointer to the AVI file
+    SDL_Surface  *surface;            // video buffer
+    
+    DWORD         dwVideoEndOffset;
+    WORD          wWidth, wHeight;    // width and height of video
+    WORD          wMsPerFrame;        // milliseconds per frame
+    DWORD         dwAudioSamplesPerSec;
+    DWORD         dwAudioBitsPerSample;
+    DWORD         dwAudioChannels;
+    SDL_AudioCVT  cvt;
+
+    SDL_mutex    *mtxAudioData;
+    BYTE          bAudioBuf[25600000];  // ring buffer for audio data
+    DWORD         dwAudioReadPos;
+    DWORD         dwAudioWritePos;
+} AVIPlayState;
+
+static SDL_mutex *gpAVIPlayStateMutex = NULL;
+static AVIPlayState *gpAVIPlayState = NULL;
+
+typedef struct
+{
+    DWORD   dwFourCC;
+    DWORD   dwSize;
+    BYTE    bData[1];
+} AVIChunk;
+
+typedef struct
+{
+    DWORD         dwMicroSecPerFrame; // frame display rate (or 0)
+    DWORD         dwMaxBytesPerSec; // max. transfer rate
+    DWORD         dwPaddingGranularity; // pad to multiples of this size
+    DWORD         dwFlags; // the ever-present flags
+    DWORD         dwTotalFrames; // # frames in file
+    DWORD         dwInitialFrames;
+    DWORD         dwStreams;
+    DWORD         dwSuggestedBufferSize;
+    DWORD         dwWidth;
+    DWORD         dwHeight;
+    DWORD         dwReserved[4];
+} MainAVIHeader;
+
+typedef struct
+{
+    DWORD         fccType;
+    DWORD         fccHandler;
+    DWORD         dwFlags;
+    WORD          wPriority;
+    WORD          wLanguage;
+    DWORD         dwInitialFrames;
+    DWORD         dwScale;
+    DWORD         dwRate; /* dwRate / dwScale == samples/second */
+    DWORD         dwStart;
+    DWORD         dwLength; /* In units above... */
+    DWORD         dwSuggestedBufferSize;
+    DWORD         dwQuality;
+    DWORD         dwSampleSize;
+    DWORD         rcFrame[4];
+} AVIStreamHeader;
+
+typedef struct
+{
+    DWORD         biSize;
+    DWORD         biWidth;
+    DWORD         biHeight;
+    WORD          biPlanes;
+    WORD          biBitCount;
+    DWORD         biCompression;
+    DWORD         biSizeImage;
+    DWORD         biXPelsPerMeter;
+    DWORD         biYPelsPerMeter;
+    DWORD         biClrUsed;
+    DWORD         biClrImportant;
+} BitmapInfoHeader;
+
+typedef struct
+{
+    WORD          wFormatTag;
+    WORD          nChannels;
+    DWORD         nSamplesPerSec;
+    DWORD         nAvgBytesPerSec;
+    WORD          nBlockAlign;
+    WORD          wBitsPerSample;
+} WaveFormat;
+
+#define AVI_RIFF (((DWORD)'R') | (((DWORD)'I') << 8) | (((DWORD)'F') << 16) | (((DWORD)'F') << 24))
+#define AVI_hdrl (((DWORD)'h') | (((DWORD)'d') << 8) | (((DWORD)'r') << 16) | (((DWORD)'l') << 24))
+#define AVI_strl (((DWORD)'s') | (((DWORD)'t') << 8) | (((DWORD)'r') << 16) | (((DWORD)'l') << 24))
+#define AVI_strh (((DWORD)'s') | (((DWORD)'t') << 8) | (((DWORD)'r') << 16) | (((DWORD)'h') << 24))
+#define AVI_strf (((DWORD)'s') | (((DWORD)'t') << 8) | (((DWORD)'r') << 16) | (((DWORD)'f') << 24))
+#define AVI_avih (((DWORD)'a') | (((DWORD)'v') << 8) | (((DWORD)'i') << 16) | (((DWORD)'h') << 24))
+#define AVI_LIST (((DWORD)'L') | (((DWORD)'I') << 8) | (((DWORD)'S') << 16) | (((DWORD)'T') << 24))
+#define AVI_movi (((DWORD)'m') | (((DWORD)'o') << 8) | (((DWORD)'v') << 16) | (((DWORD)'i') << 24))
+#define AVI_01wb (((DWORD)'0') | (((DWORD)'1') << 8) | (((DWORD)'w') << 16) | (((DWORD)'b') << 24))
+#define AVI_00dc (((DWORD)'0') | (((DWORD)'0') << 8) | (((DWORD)'d') << 16) | (((DWORD)'c') << 24))
+#define AVI_00db (((DWORD)'0') | (((DWORD)'0') << 8) | (((DWORD)'d') << 16) | (((DWORD)'b') << 24))
+#define AVI_rec  (((DWORD)'r') | (((DWORD)'e') << 8) | (((DWORD)'c') << 16) | (((DWORD)' ') << 24))
+#define AVI_JUNK (((DWORD)'J') | (((DWORD)'U') << 8) | (((DWORD)'N') << 16) | (((DWORD)'K') << 24))
+#define AVI_vids (((DWORD)'v') | (((DWORD)'i') << 8) | (((DWORD)'d') << 16) | (((DWORD)'s') << 24))
+#define AVI_auds (((DWORD)'a') | (((DWORD)'u') << 8) | (((DWORD)'d') << 16) | (((DWORD)'s') << 24))
+
+static VOID
+PAL_ParseAVInfoList(
+    AVIPlayState    *lpAVIPlayState,
+    DWORD            dwEndOffset
+)
+{
+    AVIChunk         hdr;
+    DWORD            dwNextOffset;
+    MainAVIHeader    aviHeader;
+    AVIStreamHeader  streamHdr;
+    BitmapInfoHeader bitmapHdr;
+    WaveFormat       waveFormat;
+    DWORD            dwInfoType = 0;
+
+    while (ftell(lpAVIPlayState->fp) < dwEndOffset)
+    {
+        if (feof(lpAVIPlayState->fp))
+        {
+            return; // end of file reached
+        }
+
+        fread(&hdr, sizeof(DWORD) * 2, 1, lpAVIPlayState->fp);
+        hdr.dwFourCC = SDL_SwapLE32(hdr.dwFourCC);
+        hdr.dwSize = SDL_SwapLE32(hdr.dwSize);
+
+        dwNextOffset = ftell(lpAVIPlayState->fp) + hdr.dwSize;
+
+        switch (hdr.dwFourCC)
+        {
+        case AVI_strh:
+            fread(&streamHdr, sizeof(AVIStreamHeader), 1, lpAVIPlayState->fp);
+            dwInfoType = SDL_SwapLE32(streamHdr.fccType);
+            break;
+
+        case AVI_strf:
+            if (dwInfoType == AVI_vids)
+            {
+                fread(&bitmapHdr, sizeof(bitmapHdr), 1, lpAVIPlayState->fp);
+            }
+            else if (dwInfoType == AVI_auds)
+            {
+                fread(&waveFormat, sizeof(waveFormat), 1, lpAVIPlayState->fp);
+                lpAVIPlayState->dwAudioChannels = SDL_SwapLE16(waveFormat.nChannels);
+                lpAVIPlayState->dwAudioSamplesPerSec = SDL_SwapLE16(waveFormat.nSamplesPerSec);
+                lpAVIPlayState->dwAudioBitsPerSample = SDL_SwapLE16(waveFormat.wBitsPerSample);
+            }
+            break;
+
+        default:
+            break;
+        }
+        
+        fseek(lpAVIPlayState->fp, dwNextOffset, SEEK_SET);
+    }
+
+}
+
+static VOID
+PAL_ParseHdrlList(
+    AVIPlayState    *lpAVIPlayState,
+    DWORD            dwEndOffset
+)
+{
+    AVIChunk         hdr;
+    DWORD            dwNextOffset, dwListType;
+    MainAVIHeader    aviHeader;
+
+    while (ftell(lpAVIPlayState->fp) < dwEndOffset)
+    {
+        if (feof(lpAVIPlayState->fp))
+        {
+            return; // end of file reached
+        }
+        
+        fread(&hdr, sizeof(DWORD) * 2, 1, lpAVIPlayState->fp);
+        hdr.dwFourCC = SDL_SwapLE32(hdr.dwFourCC);
+        hdr.dwSize = SDL_SwapLE32(hdr.dwSize);
+
+        dwNextOffset = ftell(lpAVIPlayState->fp) + hdr.dwSize;
+
+        switch (hdr.dwFourCC)
+        {
+        case AVI_avih:
+            fread(&aviHeader, sizeof(aviHeader), 1, lpAVIPlayState->fp);
+            lpAVIPlayState->wWidth = aviHeader.dwWidth;
+            lpAVIPlayState->wHeight = aviHeader.dwHeight;
+            lpAVIPlayState->wMsPerFrame = aviHeader.dwMicroSecPerFrame / 1000;
+            break;
+
+        case AVI_LIST:
+            fread(&dwListType, sizeof(dwListType), 1, lpAVIPlayState->fp);
+            if (SDL_SwapLE32(dwListType) == AVI_strl)
+            {
+                PAL_ParseAVInfoList(lpAVIPlayState, dwNextOffset);
+            }
+            break;
+        }
+
+        fseek(lpAVIPlayState->fp, dwNextOffset, SEEK_SET);
+    }
+}
+
+static VOID
+PAL_ReadAVIInfo(
+    AVIPlayState    *lpAVIPlayState
+)
+{
+    AVIChunk   hdr;
+    DWORD      dwListType = 0;
+    DWORD      dwNextOffset = 0;
+
+    //
+    // Skip RIFF header
+    //
+    fseek(lpAVIPlayState->fp, 12, SEEK_SET);
+    
+    while (TRUE)
+    {
+        if (feof(lpAVIPlayState->fp))
+        {
+            return; // end of file reached
+        }
+
+        fread(&hdr, sizeof(DWORD) * 2, 1, lpAVIPlayState->fp);
+        hdr.dwFourCC = SDL_SwapLE32(hdr.dwFourCC);
+        hdr.dwSize = SDL_SwapLE32(hdr.dwSize);
+        
+        dwNextOffset = ftell(lpAVIPlayState->fp) + hdr.dwSize;
+
+        switch (hdr.dwFourCC)
+        {
+        case AVI_LIST:
+            fread(&dwListType, sizeof(DWORD), 1, lpAVIPlayState->fp);
+            dwListType = SDL_SwapLE32(dwListType);
+
+            switch (dwListType)
+            {
+            case AVI_hdrl:
+                PAL_ParseHdrlList(lpAVIPlayState, dwNextOffset);
+                break;
+
+            case AVI_movi:
+                //
+                // Stop right here as the actual movie data starts
+                //
+                lpAVIPlayState->dwVideoEndOffset = dwNextOffset;
+                return;
+            }
+            
+            break;
+
+        case AVI_JUNK:
+        default:
+            //
+            // Ignore these chunks
+            //
+            break;
+        }
+
+        fseek(lpAVIPlayState->fp, dwNextOffset, SEEK_SET);
+    }
+}
+
+static AVIPlayState *
+PAL_OpenAVI(
+    LPCSTR     lpszPath
+)
+{
+    AVIPlayState *ret;
+
+    ret = (AVIPlayState *)UTIL_calloc(1, sizeof(AVIPlayState));
+
+    //
+    // Open the file
+    //
+    ret->fp = UTIL_OpenFile(lpszPath);
+    if (ret->fp == NULL)
+    {
+        fprintf(stderr, "Cannot open file: %s!\n", lpszPath);
+        free(ret);
+        return NULL;
+    }
+
+    PAL_ReadAVIInfo(ret);
+
+    if (ret->wWidth == 0 || ret->wHeight == 0)
+    {
+        return NULL;
+    }
+
+    //
+    // Create surface
+    //
+    ret->surface = SDL_CreateRGBSurface(SDL_SWSURFACE, ret->wWidth, ret->wHeight, 16,
+                                        0x7C00, 0x03E0, 0x001F, 0x0000);
+
+    //
+    // Create mutex
+    //
+    ret->mtxAudioData = SDL_CreateMutex();
+
+    //
+    // Build SDL audio conversion info
+    //
+    SDL_BuildAudioCVT(&ret->cvt,
+        (ret->dwAudioBitsPerSample == 8) ? AUDIO_U8 : AUDIO_S16LSB,
+        ret->dwAudioChannels,
+        ret->dwAudioSamplesPerSec,
+        AUDIO_S16SYS,
+        gConfig.iAudioChannels,
+        gConfig.iSampleRate);
+
+    return ret;
+}
+
+static AVIChunk *
+PAL_ReadAVChunk(
+    AVIPlayState  *lpAVIPlayState
+)
+{
+    AVIChunk   hdr;
+    AVIChunk  *ret = NULL;
+    DWORD      dwNextOffset;
+
+begin:
+    if (feof(lpAVIPlayState->fp) || ftell(lpAVIPlayState->fp) >= lpAVIPlayState->dwVideoEndOffset)
+    {
+        return NULL; // end of file reached
+    }
+
+    fread(&hdr, sizeof(DWORD) * 2, 1, lpAVIPlayState->fp);
+    hdr.dwFourCC = SDL_SwapLE32(hdr.dwFourCC);
+    hdr.dwSize = SDL_SwapLE32(hdr.dwSize);
+
+    dwNextOffset = ftell(lpAVIPlayState->fp) + hdr.dwSize;
+
+    switch (hdr.dwFourCC)
+    {
+    case AVI_LIST:
+        //
+        // Just skip list header here
+        //
+        fseek(lpAVIPlayState->fp, sizeof(DWORD), SEEK_CUR);
+        goto begin;
+
+    case AVI_01wb:
+    case AVI_00db:
+    case AVI_00dc:
+        //
+        // got actual audio/video frame
+        //
+        ret = (AVIChunk *)UTIL_malloc(sizeof(DWORD) * 2 + hdr.dwSize);
+        *ret = hdr;
+        fread(ret->bData, hdr.dwSize, 1, lpAVIPlayState->fp);
+        break;
+
+    case AVI_JUNK:
+    default:
+        //
+        // Ignore these chunks
+        //
+        fseek(lpAVIPlayState->fp, dwNextOffset, SEEK_SET);
+        goto begin;
+    }
+
+    fseek(lpAVIPlayState->fp, dwNextOffset, SEEK_SET);
+    return ret;
+}
+
+static VOID
+PAL_CloseAVI(
+    AVIPlayState  *lpAVIPlayState
+)
+{
+    if (lpAVIPlayState->fp != NULL)
+    {
+        fclose(lpAVIPlayState->fp);
+    }
+
+    if (lpAVIPlayState->surface != NULL)
+    {
+        SDL_FreeSurface(lpAVIPlayState->surface);
+    }
+
+    if (lpAVIPlayState->mtxAudioData != NULL)
+    {
+        SDL_DestroyMutex(lpAVIPlayState->mtxAudioData);
+    }
+
+    free(lpAVIPlayState);
+}
+
+static VOID
+PAL_AVIFeedAudio(
+    AVIPlayState   *lpAVIPlayState,
+    LPBYTE          lpBuffer,
+    DWORD           dwSize
+)
+{
+    SDL_mutexP(lpAVIPlayState->mtxAudioData);
+
+    while (dwSize > 0)
+    {
+        DWORD dwFeedSize = dwSize;
+
+        if (lpAVIPlayState->dwAudioWritePos + dwSize > sizeof(lpAVIPlayState->bAudioBuf))
+        {
+            dwFeedSize = sizeof(lpAVIPlayState->bAudioBuf) - lpAVIPlayState->dwAudioWritePos;
+        }
+
+        memcpy(&lpAVIPlayState->bAudioBuf[lpAVIPlayState->dwAudioWritePos], lpBuffer, dwFeedSize);
+
+        lpAVIPlayState->dwAudioWritePos += dwFeedSize;
+        lpAVIPlayState->dwAudioWritePos %= sizeof(lpAVIPlayState->bAudioBuf);
+
+        lpBuffer += dwFeedSize;
+        dwSize -= dwFeedSize;
+    }
+
+    SDL_mutexV(lpAVIPlayState->mtxAudioData);
+}
+
+VOID
+PAL_AVIInit(
+    VOID
+)
+{
+    gpAVIPlayStateMutex = SDL_CreateMutex();
+}
+
+VOID
+PAL_AVIShutdown(
+    VOID
+)
+{
+    SDL_DestroyMutex(gpAVIPlayStateMutex);
+}
+
+static VOID
+PAL_RenderAVIFrame(
+    SDL_Surface      *lpSurface,
+    AVIChunk         *lpChunk
+)
+{
+#define AV_RL16(x) ((((LPCBYTE)(x))[1] << 8) | ((LPCBYTE)(x))[0])
+#define CHECK_STREAM_PTR(n) if ((stream_ptr + n) > lpChunk->dwSize) { return; }
+    int block_ptr, pixel_ptr;
+    int total_blocks;
+    int pixel_x, pixel_y;  // pixel width and height iterators
+    int block_x, block_y;  // block width and height iterators
+    int blocks_wide, blocks_high;  // width and height in 4x4 blocks
+    int block_inc;
+    int row_dec;
+
+    /* decoding parameters */
+    int stream_ptr;
+    unsigned char byte_a, byte_b;
+    unsigned short flags;
+    int skip_blocks;
+    unsigned short colors[8];
+    unsigned short *pixels = (unsigned short *)lpSurface->pixels;
+    int stride = lpSurface->pitch / 2;
+
+    stream_ptr = 0;
+    skip_blocks = 0;
+    blocks_wide = lpSurface->w / 4;
+    blocks_high = lpSurface->h / 4;
+    total_blocks = blocks_wide * blocks_high;
+    block_inc = 4;
+    row_dec = stride + 4;
+
+    for (block_y = blocks_high; block_y > 0; block_y--)
+    {
+        block_ptr = ((block_y * 4) - 1) * stride;
+        for (block_x = blocks_wide; block_x > 0; block_x--)
+        {
+            // check if this block should be skipped
+            if (skip_blocks)
+            {
+                block_ptr += block_inc;
+                skip_blocks--;
+                total_blocks--;
+                continue;
+            }
+            
+            pixel_ptr = block_ptr;
+            
+            // get the next two bytes in the encoded data stream
+            CHECK_STREAM_PTR(2);
+            byte_a = lpChunk->bData[stream_ptr++];
+            byte_b = lpChunk->bData[stream_ptr++];
+            
+            // check if the decode is finished
+            if ((byte_a == 0) && (byte_b == 0) && (total_blocks == 0))
+            {
+                return;
+            }
+            else if ((byte_b & 0xFC) == 0x84)
+            {
+                // skip code, but don't count the current block
+                skip_blocks = ((byte_b - 0x84) << 8) + byte_a - 1;
+            }
+            else if (byte_b < 0x80)
+            {
+                // 2- or 8-color encoding modes
+                flags = (byte_b << 8) | byte_a;
+                
+                CHECK_STREAM_PTR(4);
+                colors[0] = AV_RL16(&lpChunk->bData[stream_ptr]);
+                stream_ptr += 2;
+                colors[1] = AV_RL16(&lpChunk->bData[stream_ptr]);
+                stream_ptr += 2;
+                
+                if (colors[0] & 0x8000)
+                {
+                    // 8-color encoding
+                    CHECK_STREAM_PTR(12);
+                    colors[2] = AV_RL16(&lpChunk->bData[stream_ptr]);
+                    stream_ptr += 2;
+                    colors[3] = AV_RL16(&lpChunk->bData[stream_ptr]);
+                    stream_ptr += 2;
+                    colors[4] = AV_RL16(&lpChunk->bData[stream_ptr]);
+                    stream_ptr += 2;
+                    colors[5] = AV_RL16(&lpChunk->bData[stream_ptr]);
+                    stream_ptr += 2;
+                    colors[6] = AV_RL16(&lpChunk->bData[stream_ptr]);
+                    stream_ptr += 2;
+                    colors[7] = AV_RL16(&lpChunk->bData[stream_ptr]);
+                    stream_ptr += 2;
+                    
+                    for (pixel_y = 0; pixel_y < 4; pixel_y++)
+                    {
+                        for (pixel_x = 0; pixel_x < 4; pixel_x++, flags >>= 1)
+                        {
+                            pixels[pixel_ptr++] =
+                            colors[((pixel_y & 0x2) << 1) +
+                                   (pixel_x & 0x2) + ((flags & 0x1) ^ 1)];
+                        }
+                        pixel_ptr -= row_dec;
+                    }
+                }
+                else
+                {
+                    // 2-color encoding
+                    for (pixel_y = 0; pixel_y < 4; pixel_y++)
+                    {
+                        for (pixel_x = 0; pixel_x < 4; pixel_x++, flags >>= 1)
+                        {
+                            pixels[pixel_ptr++] = colors[(flags & 0x1) ^ 1];
+                        }
+                        pixel_ptr -= row_dec;
+                    }
+                }
+            }
+            else
+            {
+                // otherwise, it's a 1-color block
+                colors[0] = (byte_b << 8) | byte_a;
+
+                for (pixel_y = 0; pixel_y < 4; pixel_y++)
+                {
+                    for (pixel_x = 0; pixel_x < 4; pixel_x++)
+                    {
+                        pixels[pixel_ptr++] = colors[0];
+                    }
+                    pixel_ptr -= row_dec;
+                }
+            }
+            
+            block_ptr += block_inc;
+            total_blocks--;
+        }
+    }
+}
+
+BOOL
+PAL_PlayAVI(
+    LPCSTR     lpszPath
+)
+{
+    AVIPlayState *avi;
+    AVIChunk     *chunk;
+    DWORD         dwNextFrameTime = 0, dwCurrentTime = 0;
+    BOOL          fEndPlay = FALSE;
+
+    //
+    // Open AVI file
+    //
+    avi = PAL_OpenAVI(lpszPath);
+    if (avi == NULL)
+    {
+        return FALSE;
+    }
+
+    SDL_mutexP(gpAVIPlayStateMutex);
+    gpAVIPlayState = avi;
+    SDL_mutexV(gpAVIPlayStateMutex);
+
+    PAL_ClearKeyState();
+
+    while (!fEndPlay && (chunk = PAL_ReadAVChunk(avi)) != NULL)
+    {
+        dwCurrentTime = SDL_GetTicks();
+
+        switch (chunk->dwFourCC)
+        {
+        case AVI_00dc:
+        case AVI_00db:
+            //
+            // Video frame
+            //
+            PAL_RenderAVIFrame(avi->surface, chunk);
+            VIDEO_DrawSurfaceToScreen(avi->surface);
+
+            dwNextFrameTime = dwCurrentTime + avi->wMsPerFrame;
+            dwCurrentTime = SDL_GetTicks();
+            if (dwCurrentTime >= dwNextFrameTime)
+            {
+                UTIL_Delay(1);
+            }
+            else
+            {
+                UTIL_Delay(dwNextFrameTime - dwCurrentTime);
+            }
+
+            if (g_InputState.dwKeyPress & (kKeyMenu | kKeySearch))
+            {
+                fEndPlay = TRUE;
+            }
+            break;
+
+        case AVI_01wb:
+            //
+            // Audio data, just feed into buffer
+            //
+            PAL_AVIFeedAudio(avi, chunk->bData, chunk->dwSize);
+            break;
+        }
+
+        free(chunk);
+    }
+
+    SDL_mutexP(gpAVIPlayStateMutex);
+    gpAVIPlayState = NULL;
+    SDL_mutexV(gpAVIPlayStateMutex);
+
+    if (fEndPlay)
+    {
+        //
+        // Simulate a short delay (like the original game)
+        //
+        UTIL_Delay(500);
+    }
+
+    PAL_CloseAVI(avi);
+    return TRUE;
+}
+
+VOID SDLCALL
+AVI_FillAudioBuffer(
+    LPVOID          udata,
+    LPBYTE          stream,
+    INT             len
+)
+{
+    SDL_mutexP(gpAVIPlayStateMutex);
+    if (gpAVIPlayState != NULL)
+    {
+        FLOAT flRateScale = ((FLOAT)gConfig.iSampleRate / gpAVIPlayState->dwAudioSamplesPerSec);
+        while (len > 0 && gpAVIPlayState->dwAudioReadPos != gpAVIPlayState->dwAudioWritePos)
+        {
+            INT remainingLen = gpAVIPlayState->dwAudioWritePos - gpAVIPlayState->dwAudioReadPos;
+            INT samplesToRead;
+
+            if (remainingLen < 0)
+            {
+                remainingLen = sizeof(gpAVIPlayState->bAudioBuf) - gpAVIPlayState->dwAudioReadPos;
+            }
+
+            samplesToRead = remainingLen / gpAVIPlayState->dwAudioChannels / (gpAVIPlayState->dwAudioBitsPerSample / 8);
+            if (samplesToRead > len / 2 / gConfig.iAudioChannels / flRateScale)
+            {
+                samplesToRead = len / 2 / gConfig.iAudioChannels / flRateScale;
+            }
+
+            gpAVIPlayState->cvt.buf = stream;
+            gpAVIPlayState->cvt.len = samplesToRead *
+                gpAVIPlayState->dwAudioChannels * (gpAVIPlayState->dwAudioBitsPerSample / 8);
+
+            memcpy(stream, &gpAVIPlayState->bAudioBuf[gpAVIPlayState->dwAudioReadPos],
+                samplesToRead * gpAVIPlayState->dwAudioChannels * (gpAVIPlayState->dwAudioBitsPerSample / 8));
+
+            SDL_ConvertAudio(&gpAVIPlayState->cvt);
+
+            stream += (DWORD)(samplesToRead * 2 * gConfig.iAudioChannels * flRateScale);
+            len -= samplesToRead * 2 * gConfig.iAudioChannels * flRateScale;
+
+            gpAVIPlayState->dwAudioReadPos += samplesToRead * gpAVIPlayState->dwAudioChannels *
+                (gpAVIPlayState->dwAudioBitsPerSample / 8);
+
+            gpAVIPlayState->dwAudioReadPos %= sizeof(gpAVIPlayState->bAudioBuf);
+        }
+    }
+    SDL_mutexV(gpAVIPlayStateMutex);
+}

+ 40 - 0
aviplay.h

@@ -0,0 +1,40 @@
+//
+//  aviplay.h
+//  Pal
+//
+//  Created by Wei Mingzhi on 5/26/17.
+//
+//
+
+#ifndef AVIPLAY_H
+#define AVIPLAY_H
+
+#include "common.h"
+
+PAL_C_LINKAGE_BEGIN
+
+VOID
+PAL_AVIInit(
+    VOID
+);
+
+VOID
+PAL_AVIShutdown(
+    VOID
+);
+
+BOOL
+PAL_PlayAVI(
+    LPCSTR     lpszPath
+);
+
+VOID SDLCALL
+AVI_FillAudioBuffer(
+    LPVOID          udata,
+    LPBYTE          stream,
+    INT             len
+);
+
+PAL_C_LINKAGE_END
+
+#endif

+ 25 - 0
ending.c

@@ -397,7 +397,32 @@ VOID
 PAL_EndingScreen(
    VOID
 )
+/*++
+ Purpose:
+ 
+   Show the ending screen for Win95 version.
+
+ Parameters:
+
+   None.
+
+ Return value:
+
+   None.
+
+--*/
 {
+    //
+    // Use AVI if we can
+    //
+    if (PAL_PlayAVI("4.avi") && PAL_PlayAVI("5.avi") && PAL_PlayAVI("6.avi"))
+    {
+        return;
+    }
+
+    //
+    // Otherwise, simulate the ending of DOS version
+    //
 	AUDIO_PlayMusic(0x1a, TRUE, 0);
 	PAL_RNGPlay(gpGlobals->iCurPlayingRNG, 110, 150, 7);
 	PAL_RNGPlay(gpGlobals->iCurPlayingRNG, 151, 999, 9);

+ 6 - 0
macos/Pal.xcodeproj/project.pbxproj

@@ -66,6 +66,7 @@
 		7104FDB40D772FBC00A97E53 /* player.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7104FDA50D772FBC00A97E53 /* player.cpp */; };
 		7104FDB60D772FBC00A97E53 /* rix.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7104FDA70D772FBC00A97E53 /* rix.cpp */; };
 		71147E4014085E31003FB2DB /* surroundopl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 71147E3E14085E31003FB2DB /* surroundopl.cpp */; };
+		713765641ED86C930084F495 /* aviplay.c in Sources */ = {isa = PBXBuildFile; fileRef = 713765621ED86C930084F495 /* aviplay.c */; };
 		7138FD0F1424E4810060DE76 /* dosbox_opl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7138FD0C1424E4810060DE76 /* dosbox_opl.cpp */; };
 		716EB9BC0D77318900D5DE1F /* game.c in Sources */ = {isa = PBXBuildFile; fileRef = 7104FD3F0D772F6300A97E53 /* game.c */; };
 		716EB9C70D77340300D5DE1F /* sdlpal.icns in Resources */ = {isa = PBXBuildFile; fileRef = 716EB9C60D77340300D5DE1F /* sdlpal.icns */; };
@@ -317,6 +318,8 @@
 		7104FDA80D772FBC00A97E53 /* rix.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = rix.h; path = adplug/rix.h; sourceTree = "<group>"; };
 		71147E3E14085E31003FB2DB /* surroundopl.cpp */ = {isa = PBXFileReference; fileEncoding = 0; lastKnownFileType = sourcecode.cpp.cpp; name = surroundopl.cpp; path = adplug/surroundopl.cpp; sourceTree = "<group>"; };
 		71147E3F14085E31003FB2DB /* surroundopl.h */ = {isa = PBXFileReference; fileEncoding = 0; lastKnownFileType = sourcecode.c.h; name = surroundopl.h; path = adplug/surroundopl.h; sourceTree = "<group>"; };
+		713765621ED86C930084F495 /* aviplay.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = aviplay.c; sourceTree = "<group>"; };
+		713765631ED86C930084F495 /* aviplay.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = aviplay.h; sourceTree = "<group>"; };
 		7138FD0B1424E4810060DE76 /* demuopl.h */ = {isa = PBXFileReference; fileEncoding = 18446744071562067968; lastKnownFileType = sourcecode.c.h; name = demuopl.h; path = adplug/demuopl.h; sourceTree = "<group>"; };
 		7138FD0C1424E4810060DE76 /* dosbox_opl.cpp */ = {isa = PBXFileReference; fileEncoding = 18446744071562067968; lastKnownFileType = sourcecode.cpp.cpp; name = dosbox_opl.cpp; path = adplug/dosbox_opl.cpp; sourceTree = "<group>"; };
 		7138FD0D1424E4810060DE76 /* dosbox_opl.h */ = {isa = PBXFileReference; fileEncoding = 18446744071562067968; lastKnownFileType = sourcecode.c.h; name = dosbox_opl.h; path = adplug/dosbox_opl.h; sourceTree = "<group>"; };
@@ -469,6 +472,7 @@
 		29B97315FDCFA39411CA2CEA /* Sources */ = {
 			isa = PBXGroup;
 			children = (
+				713765621ED86C930084F495 /* aviplay.c */,
 				7104FD3A0D772F6300A97E53 /* battle.c */,
 				71F0F6D10DAA63B400F88C16 /* ending.c */,
 				71E27E030D8C7E2F0048BA16 /* fight.c */,
@@ -780,6 +784,7 @@
 			children = (
 				C626FFC31E5BD49100E39DD9 /* pal_config.h */,
 				7104FD390D772F6300A97E53 /* ascii.h */,
+				713765631ED86C930084F495 /* aviplay.h */,
 				7104FD3B0D772F6300A97E53 /* battle.h */,
 				5757D2981B7A3E0300464A74 /* codepage.h */,
 				7104FD3C0D772F6300A97E53 /* common.h */,
@@ -1073,6 +1078,7 @@
 				C63DF68B1EC50B7300C778B8 /* util.mm in Sources */,
 				71E23EA913F6D1AD001287B6 /* stream.c in Sources */,
 				71E23EAB13F6D1AD001287B6 /* synth.c in Sources */,
+				713765641ED86C930084F495 /* aviplay.c in Sources */,
 				5757D2961B7A3DCE00464A74 /* oggplay.c in Sources */,
 				71E23EAD13F6D1AD001287B6 /* timer.c in Sources */,
 				5757D2741B7A3D5800464A74 /* codebook.c in Sources */,

+ 17 - 0
main.c

@@ -100,6 +100,7 @@ PAL_Init(
    PAL_InitInput();
    PAL_InitResources();
    AUDIO_OpenDevice();
+   PAL_AVIInit();
 
    VIDEO_SetWindowTitle(va("Pal %s%s%s",
 	   gConfig.fIsWIN95 ? "Win95" : "DOS",
@@ -136,6 +137,7 @@ PAL_Shutdown(
 --*/
 {
    AUDIO_CloseDevice();
+   PAL_AVIShutdown();
    PAL_FreeFont();
    PAL_FreeResources();
    PAL_FreeGlobals();
@@ -169,6 +171,13 @@ PAL_TrademarkScreen(
 
 --*/
 {
+   if (gConfig.fIsWIN95)
+   {
+      if (PAL_PlayAVI("1.avi"))
+      {
+         return;
+      }
+   }
    PAL_SetPalette(3, FALSE);
    PAL_RNGPlay(6, 0, 1000, 25);
    UTIL_Delay(1000);
@@ -205,6 +214,14 @@ PAL_SplashScreen(
    DWORD          dwTime, dwBeginTime;
    BOOL           fUseCD = TRUE;
 
+   if (gConfig.fIsWIN95)
+   {
+      if (PAL_PlayAVI("2.avi"))
+      {
+         return;
+      }
+   }
+
    if (palette == NULL)
    {
       fprintf(stderr, "ERROR: PAL_SplashScreen(): palette == NULL\n");

+ 1 - 0
main.h

@@ -51,6 +51,7 @@
 #include "play.h"
 #include "game.h"
 #include "midi.h"
+#include "aviplay.h"
 
 VOID
 PAL_Shutdown(

+ 5 - 0
uigame.c

@@ -156,6 +156,11 @@ PAL_OpeningMenu(
    AUDIO_PlayMusic(0, FALSE, 1);
    PAL_FadeOut(1);
 
+   if (gConfig.fIsWIN95 && wItemSelected == 0)
+   {
+      PAL_PlayAVI("3.avi");
+   }
+
    return (INT)wItemSelected;
 }
 

+ 49 - 0
video.c

@@ -1177,3 +1177,52 @@ VIDEO_UpdateSurfacePalette(
 	SDL_SetPalette(pSurface, SDL_PHYSPAL | SDL_LOGPAL, gpPalette->colors, 0, 256);
 #endif
 }
+
+VOID
+VIDEO_DrawSurfaceToScreen(
+    SDL_Surface    *pSurface
+)
+/*++
+  Purpose:
+
+    Draw a surface directly to screen.
+
+  Parameters:
+
+    [IN]  pSurface - the surface which needs to be drawn to screen.
+
+  Return value:
+
+    None.
+
+--*/
+{
+#if SDL_VERSION_ATLEAST(2, 0, 0)
+   //
+   // Draw the surface to screen.
+   //
+   SDL_BlitScaled(pSurface, NULL, gpScreenReal, NULL);
+   VIDEO_RenderCopy();
+#else
+   SDL_Surface   *pCompatSurface;
+   SDL_Rect       rect;
+
+   rect.x = rect.y = 0;
+   rect.w = pSurface->w;
+   rect.h = pSurface->h;
+
+   pCompatSurface = VIDEO_CreateCompatibleSizedSurface(gpScreenReal, &rect);
+
+   //
+   // First convert the surface to compatible format.
+   //
+   SDL_BlitSurface(pSurface, NULL, pCompatSurface, NULL);
+
+   //
+   // Draw the surface to screen.
+   //
+   SDL_SoftStretch(pCompatSurface, NULL, gpScreenReal, NULL);
+   SDL_UpdateRect(gpScreenReal, 0, 0, gpScreenReal->w, gpScreenReal->h);
+   SDL_FreeSurface(pCompatSurface);
+#endif
+}

+ 5 - 0
video.h

@@ -121,6 +121,11 @@ VIDEO_UpdateSurfacePalette(
 	SDL_Surface    *pSurface
 );
 
+VOID
+VIDEO_DrawSurfaceToScreen(
+    SDL_Surface    *pSurface
+);
+
 PAL_C_LINKAGE_END
 
 #endif

+ 1 - 1
yj1.c

@@ -23,7 +23,7 @@
 // Copyright (c) 2006-2007, Lou Yihua.
 //
 // Ported to C from C++ and modified for compatibility with Big-Endian
-// by Wei Mingzhi <whistler@openoffice.org>.
+// by Wei Mingzhi <whistler_wmz@users.sf.net>.
 //
 
 #include "common.h"