[FFmpeg-cvslog] HNM4/HNM4A demuxer & video decoder

David Kment git at videolan.org
Fri Nov 1 10:56:03 CET 2013


ffmpeg | branch: master | David Kment <info at davidkment.de> | Mon Oct 28 01:38:02 2013 +0100| [9af7a8523a6bb517834ebed36093bdab11a8b38e] | committer: Diego Biurrun

HNM4/HNM4A demuxer & video decoder

Signed-off-by: Diego Biurrun <diego at biurrun.de>

> http://git.videolan.org/gitweb.cgi/ffmpeg.git/?a=commit;h=9af7a8523a6bb517834ebed36093bdab11a8b38e
---

 Changelog                |    1 +
 doc/general.texi         |    3 +
 libavcodec/Makefile      |    1 +
 libavcodec/allcodecs.c   |    1 +
 libavcodec/avcodec.h     |    1 +
 libavcodec/codec_desc.c  |    7 +
 libavcodec/hnm4video.c   |  459 ++++++++++++++++++++++++++++++++++++++++++++++
 libavcodec/version.h     |    2 +-
 libavformat/Makefile     |    1 +
 libavformat/allformats.c |    1 +
 libavformat/hnm.c        |  204 +++++++++++++++++++++
 libavformat/version.h    |    2 +-
 12 files changed, 681 insertions(+), 2 deletions(-)

diff --git a/Changelog b/Changelog
index 4032d07..de622d3 100644
--- a/Changelog
+++ b/Changelog
@@ -40,6 +40,7 @@ version 10:
 - Opus in Ogg demuxing
 - Enhanced Low Delay AAC (ER AAC ELD) decoding (no LD SBR support)
 - F4V muxer
+- HNM version 4 demuxer and video decoder
 
 
 version 9:
diff --git a/doc/general.texi b/doc/general.texi
index c2dc272..c50e89f 100644
--- a/doc/general.texi
+++ b/doc/general.texi
@@ -194,6 +194,8 @@ library:
 @item GXF                       @tab X @tab X
     @tab General eXchange Format SMPTE 360M, used by Thomson Grass Valley
          playout servers.
+ at item HNM @tab   @tab X
+    @tab Only version 4 supported, used in some games from Cryo Interactive
 @item id Quake II CIN video     @tab   @tab X
 @item id RoQ                    @tab X @tab X
     @tab Used in Quake III, Jedi Knight 2, other computer games.
@@ -524,6 +526,7 @@ following image formats are supported:
 @item H.263+ / H.263-1998 / H.263 version 2  @tab  X  @tab  X
 @item H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10  @tab  E  @tab  X
     @tab encoding supported through external library libx264
+ at item HNM version 4          @tab     @tab  X
 @item HuffYUV                @tab  X  @tab  X
 @item HuffYUV FFmpeg variant @tab  X  @tab  X
 @item IBM Ultimotion         @tab     @tab  X
diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index 6f80a9e..ffee97a 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -196,6 +196,7 @@ OBJS-$(CONFIG_H264_DECODER)            += h264.o                               \
                                           h264_loopfilter.o h264_direct.o      \
                                           cabac.o h264_sei.o h264_ps.o         \
                                           h264_refs.o h264_cavlc.o h264_cabac.o
+OBJS-$(CONFIG_HNM4_VIDEO_DECODER)      += hnm4video.o
 OBJS-$(CONFIG_HUFFYUV_DECODER)         += huffyuv.o huffyuvdec.o
 OBJS-$(CONFIG_HUFFYUV_ENCODER)         += huffyuv.o huffyuvenc.o
 OBJS-$(CONFIG_IAC_DECODER)             += imc.o
diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c
index 55d7957..b62efb9 100644
--- a/libavcodec/allcodecs.c
+++ b/libavcodec/allcodecs.c
@@ -153,6 +153,7 @@ void avcodec_register_all(void)
     REGISTER_DECODER(H263I,             h263i);
     REGISTER_ENCODER(H263P,             h263p);
     REGISTER_DECODER(H264,              h264);
+    REGISTER_DECODER(HNM4_VIDEO,        hnm4_video);
     REGISTER_ENCDEC (HUFFYUV,           huffyuv);
     REGISTER_DECODER(IDCIN,             idcin);
     REGISTER_DECODER(IFF_BYTERUN1,      iff_byterun1);
diff --git a/libavcodec/avcodec.h b/libavcodec/avcodec.h
index ad45d97..6cd7076 100644
--- a/libavcodec/avcodec.h
+++ b/libavcodec/avcodec.h
@@ -274,6 +274,7 @@ enum AVCodecID {
     AV_CODEC_ID_ESCAPE130,
     AV_CODEC_ID_G2M,
     AV_CODEC_ID_WEBP,
+    AV_CODEC_ID_HNM4_VIDEO,
 
     /* various PCM "codecs" */
     AV_CODEC_ID_FIRST_AUDIO = 0x10000,     ///< A dummy id pointing at the start of audio codecs
diff --git a/libavcodec/codec_desc.c b/libavcodec/codec_desc.c
index f486cb2..ddeca8a 100644
--- a/libavcodec/codec_desc.c
+++ b/libavcodec/codec_desc.c
@@ -1236,6 +1236,13 @@ static const AVCodecDescriptor codec_descriptors[] = {
         .props     = AV_CODEC_PROP_INTRA_ONLY | AV_CODEC_PROP_LOSSY |
                      AV_CODEC_PROP_LOSSLESS,
     },
+    {
+        .id        = AV_CODEC_ID_HNM4_VIDEO,
+        .type      = AVMEDIA_TYPE_VIDEO,
+        .name      = "hnm4video",
+        .long_name = NULL_IF_CONFIG_SMALL("HNM 4 video"),
+        .props     = AV_CODEC_PROP_LOSSY,
+    },
 
     /* various PCM "codecs" */
     {
diff --git a/libavcodec/hnm4video.c b/libavcodec/hnm4video.c
new file mode 100644
index 0000000..b200e89
--- /dev/null
+++ b/libavcodec/hnm4video.c
@@ -0,0 +1,459 @@
+/*
+ * Cryo Interactive Entertainment HNM4 video decoder
+ *
+ * Copyright (c) 2012 David Kment
+ *
+ * This file is part of Libav.
+ *
+ * Libav 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.
+ *
+ * Libav 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 Libav; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <string.h>
+
+#include "libavutil/internal.h"
+#include "libavutil/intreadwrite.h"
+#include "libavutil/mem.h"
+#include "avcodec.h"
+#include "bytestream.h"
+#include "internal.h"
+
+#define HNM4_CHUNK_ID_PL 19536
+#define HNM4_CHUNK_ID_IZ 23113
+#define HNM4_CHUNK_ID_IU 21833
+#define HNM4_CHUNK_ID_SD 17491
+
+typedef struct Hnm4VideoContext {
+    uint8_t version;
+    uint16_t width;
+    uint16_t height;
+    uint8_t *current;
+    uint8_t *previous;
+    uint8_t *buffer1;
+    uint8_t *buffer2;
+    uint8_t *processed;
+    uint32_t palette[256];
+} Hnm4VideoContext;
+
+static int getbit(GetByteContext *gb, uint32_t *bitbuf, int *bits)
+{
+    int ret;
+
+    if (!*bits) {
+        *bitbuf = bytestream2_get_le32(gb);
+        *bits = 32;
+    }
+
+    ret = *bitbuf >> 31;
+    *bitbuf <<= 1;
+    (*bits)--;
+
+    return ret;
+}
+
+static void unpack_intraframe(AVCodecContext *avctx, uint8_t *src,
+                              uint32_t size)
+{
+    Hnm4VideoContext *hnm = avctx->priv_data;
+    GetByteContext gb;
+    uint32_t bitbuf = 0, writeoffset = 0, count = 0;
+    uint16_t word;
+    int32_t offset;
+    int bits = 0;
+
+    bytestream2_init(&gb, src, size);
+
+    while (bytestream2_tell(&gb) < size) {
+        if (getbit(&gb, &bitbuf, &bits)) {
+            if (writeoffset >= hnm->width * hnm->height) {
+                av_log(avctx, AV_LOG_ERROR,
+                       "Attempting to write out of bounds");
+                break;
+            }
+            hnm->current[writeoffset++] = bytestream2_get_byte(&gb);
+        } else {
+            if (getbit(&gb, &bitbuf, &bits)) {
+                word   = bytestream2_get_le16(&gb);
+                count  = word & 0x07;
+                offset = (word >> 3) - 0x2000;
+                if (!count)
+                    count = bytestream2_get_byte(&gb);
+                if (!count)
+                    return;
+            } else {
+                count  = getbit(&gb, &bitbuf, &bits) * 2;
+                count += getbit(&gb, &bitbuf, &bits);
+                offset = bytestream2_get_byte(&gb) - 0x0100;
+            }
+            count  += 2;
+            offset += writeoffset;
+            if (offset < 0 || offset + count >= hnm->width * hnm->height) {
+                av_log(avctx, AV_LOG_ERROR, "Attempting to read out of bounds");
+                break;
+            } else if (writeoffset + count >= hnm->width * hnm->height) {
+                av_log(avctx, AV_LOG_ERROR,
+                       "Attempting to write out of bounds");
+                break;
+            }
+            while (count--) {
+                hnm->current[writeoffset++] = hnm->current[offset++];
+            }
+        }
+    }
+}
+
+static void postprocess_current_frame(AVCodecContext *avctx)
+{
+    Hnm4VideoContext *hnm = avctx->priv_data;
+    uint32_t x, y, src_x, src_y;
+
+    for (y = 0; y < hnm->height; y++) {
+        src_y = y - (y % 2);
+        src_x = src_y * hnm->width + (y % 2);
+        for (x = 0; x < hnm->width; x++) {
+            hnm->processed[(y * hnm->width) + x] = hnm->current[src_x];
+            src_x += 2;
+        }
+    }
+}
+
+static void copy_processed_frame(AVCodecContext *avctx, AVFrame *frame)
+{
+    Hnm4VideoContext *hnm = avctx->priv_data;
+    uint8_t *src = hnm->processed;
+    uint8_t *dst = frame->data[0];
+    int y;
+
+    for (y = 0; y < hnm->height; y++) {
+        memcpy(dst, src, hnm->width);
+        src += hnm->width;
+        dst += frame->linesize[0];
+    }
+}
+
+static void decode_interframe_v4(AVCodecContext *avctx, uint8_t *src, uint32_t size)
+{
+    Hnm4VideoContext *hnm = avctx->priv_data;
+    GetByteContext gb;
+    uint32_t writeoffset = 0, count, left, offset;
+    uint8_t tag, previous, backline, backward, swap;
+
+    bytestream2_init(&gb, src, size);
+
+    while (bytestream2_tell(&gb) < size) {
+        count = bytestream2_peek_byte(&gb) & 0x1F;
+        if (count == 0) {
+            tag = bytestream2_get_byte(&gb) & 0xE0;
+            tag = tag >> 5;
+            if (tag == 0) {
+                hnm->current[writeoffset++] = bytestream2_get_byte(&gb);
+                hnm->current[writeoffset++] = bytestream2_get_byte(&gb);
+            } else if (tag == 1) {
+                writeoffset += bytestream2_get_byte(&gb) * 2;
+            } else if (tag == 2) {
+                count = bytestream2_get_le16(&gb);
+                count *= 2;
+                writeoffset += count;
+            } else if (tag == 3) {
+                count = bytestream2_get_byte(&gb) * 2;
+                while (count > 0) {
+                    hnm->current[writeoffset++] = bytestream2_peek_byte(&gb);
+                    count--;
+                }
+                bytestream2_skip(&gb, 1);
+            } else {
+                break;
+            }
+        } else {
+            previous = bytestream2_peek_byte(&gb) & 0x20;
+            backline = bytestream2_peek_byte(&gb) & 0x40;
+            backward = bytestream2_peek_byte(&gb) & 0x80;
+            bytestream2_skip(&gb, 1);
+            swap   = bytestream2_peek_byte(&gb) & 0x01;
+            offset = bytestream2_get_le16(&gb);
+            offset = (offset >> 1) & 0x7FFF;
+            offset = writeoffset + (offset * 2) - 0x8000;
+
+            left = count;
+
+            if (!backward && offset + count >= hnm->width * hnm->height) {
+                av_log(avctx, AV_LOG_ERROR, "Attempting to read out of bounds");
+                break;
+            } else if (backward && offset >= hnm->width * hnm->height) {
+                av_log(avctx, AV_LOG_ERROR, "Attempting to read out of bounds");
+                break;
+            } else if (writeoffset + count >= hnm->width * hnm->height) {
+                av_log(avctx, AV_LOG_ERROR,
+                       "Attempting to write out of bounds");
+                break;
+            }
+
+            if (previous) {
+                while (left > 0) {
+                    if (backline) {
+                        hnm->current[writeoffset++] = hnm->previous[offset - (2 * hnm->width) + 1];
+                        hnm->current[writeoffset++] = hnm->previous[offset++];
+                        offset++;
+                    } else {
+                        hnm->current[writeoffset++] = hnm->previous[offset++];
+                        hnm->current[writeoffset++] = hnm->previous[offset++];
+                    }
+                    if (backward)
+                        offset -= 4;
+                    left--;
+                }
+            } else {
+                while (left > 0) {
+                    if (backline) {
+                        hnm->current[writeoffset++] = hnm->current[offset - (2 * hnm->width) + 1];
+                        hnm->current[writeoffset++] = hnm->current[offset++];
+                        offset++;
+                    } else {
+                        hnm->current[writeoffset++] = hnm->current[offset++];
+                        hnm->current[writeoffset++] = hnm->current[offset++];
+                    }
+                    if (backward)
+                        offset -= 4;
+                    left--;
+                }
+            }
+
+            if (swap) {
+                left         = count;
+                writeoffset -= count * 2;
+                while (left > 0) {
+                    swap = hnm->current[writeoffset];
+                    hnm->current[writeoffset] = hnm->current[writeoffset + 1];
+                    hnm->current[writeoffset + 1] = swap;
+                    left--;
+                    writeoffset += 2;
+                }
+            }
+        }
+    }
+}
+
+static void decode_interframe_v4a(AVCodecContext *avctx, uint8_t *src,
+                                  uint32_t size)
+{
+    Hnm4VideoContext *hnm = avctx->priv_data;
+    GetByteContext gb;
+    uint32_t writeoffset = 0, offset;
+    uint8_t tag, count, previous, delta;
+
+    bytestream2_init(&gb, src, size);
+
+    while (bytestream2_tell(&gb) < size) {
+        count = bytestream2_peek_byte(&gb) & 0x3F;
+        if (count == 0) {
+            tag = bytestream2_get_byte(&gb) & 0xC0;
+            tag = tag >> 6;
+            if (tag == 0) {
+                writeoffset += bytestream2_get_byte(&gb);
+            } else if (tag == 1) {
+                hnm->current[writeoffset]              = bytestream2_get_byte(&gb);
+                hnm->current[writeoffset + hnm->width] = bytestream2_get_byte(&gb);
+                writeoffset++;
+            } else if (tag == 2) {
+                writeoffset += hnm->width;
+            } else if (tag == 3) {
+                break;
+            }
+        } else {
+            delta    = bytestream2_peek_byte(&gb) & 0x80;
+            previous = bytestream2_peek_byte(&gb) & 0x40;
+            bytestream2_skip(&gb, 1);
+
+            offset  = writeoffset;
+            offset += bytestream2_get_le16(&gb);
+
+            if (delta)
+                offset -= 0x10000;
+
+            if (offset + hnm->width + count >= hnm->width * hnm->height) {
+                av_log(avctx, AV_LOG_ERROR, "Attempting to read out of bounds");
+                break;
+            } else if (writeoffset + hnm->width + count >= hnm->width * hnm->height) {
+                av_log(avctx, AV_LOG_ERROR, "Attempting to write out of bounds");
+                break;
+            }
+
+            if (previous) {
+                while (count > 0) {
+                    hnm->current[writeoffset]              = hnm->previous[offset];
+                    hnm->current[writeoffset + hnm->width] = hnm->previous[offset + hnm->width];
+                    writeoffset++;
+                    offset++;
+                    count--;
+                }
+            } else {
+                while (count > 0) {
+                    hnm->current[writeoffset]              = hnm->current[offset];
+                    hnm->current[writeoffset + hnm->width] = hnm->current[offset + hnm->width];
+                    writeoffset++;
+                    offset++;
+                    count--;
+                }
+            }
+        }
+    }
+}
+
+static void hnm_update_palette(AVCodecContext *avctx, uint8_t *src,
+                               uint32_t size)
+{
+    Hnm4VideoContext *hnm = avctx->priv_data;
+    GetByteContext gb;
+    uint8_t start, writeoffset;
+    uint16_t count;
+    int eight_bit_colors;
+
+    eight_bit_colors = src[7] & 0x80 && hnm->version == 0x4a;
+
+    // skip first 8 bytes
+    bytestream2_init(&gb, src + 8, size - 8);
+
+    while (bytestream2_tell(&gb) < size - 8) {
+        start = bytestream2_get_byte(&gb);
+        count = bytestream2_get_byte(&gb);
+        if (start == 255 && count == 255)
+            break;
+        if (count == 0)
+            count = 256;
+        writeoffset = start;
+        while (count > 0) {
+            hnm->palette[writeoffset] = bytestream2_get_be24(&gb);
+            if (!eight_bit_colors)
+                hnm->palette[writeoffset] <<= 2;
+            count--;
+            writeoffset++;
+        }
+    }
+}
+
+static void hnm_flip_buffers(Hnm4VideoContext *hnm)
+{
+    uint8_t *temp;
+
+    temp          = hnm->current;
+    hnm->current  = hnm->previous;
+    hnm->previous = temp;
+}
+
+static int hnm_decode_frame(AVCodecContext *avctx, void *data,
+                            int *got_frame, AVPacket *avpkt)
+{
+    AVFrame *frame = data;
+    Hnm4VideoContext *hnm = avctx->priv_data;
+    int ret;
+    uint16_t chunk_id;
+
+    if ((ret = ff_get_buffer(avctx, frame, 0)) < 0) {
+        av_log(avctx, AV_LOG_ERROR, "get_buffer() failed\n");
+        return ret;
+    }
+
+    chunk_id = AV_RL16(avpkt->data + 4);
+
+    if (chunk_id == HNM4_CHUNK_ID_PL) {
+        hnm_update_palette(avctx, avpkt->data, avpkt->size);
+        frame->palette_has_changed = 1;
+    } else if (chunk_id == HNM4_CHUNK_ID_IZ) {
+        unpack_intraframe(avctx, avpkt->data + 12, avpkt->size - 12);
+        memcpy(hnm->previous, hnm->current, hnm->width * hnm->height);
+        if (hnm->version == 0x4a)
+            memcpy(hnm->processed, hnm->current, hnm->width * hnm->height);
+        else
+            postprocess_current_frame(avctx);
+        copy_processed_frame(avctx, frame);
+        frame->pict_type = AV_PICTURE_TYPE_I;
+        frame->key_frame = 1;
+        memcpy(frame->data[1], hnm->palette, 256 * 4);
+        *got_frame = 1;
+    } else if (chunk_id == HNM4_CHUNK_ID_IU) {
+        if (hnm->version == 0x4a) {
+            decode_interframe_v4a(avctx, avpkt->data + 8, avpkt->size - 8);
+            memcpy(hnm->processed, hnm->current, hnm->width * hnm->height);
+        } else {
+            decode_interframe_v4(avctx, avpkt->data + 8, avpkt->size - 8);
+            postprocess_current_frame(avctx);
+        }
+        copy_processed_frame(avctx, frame);
+        frame->pict_type = AV_PICTURE_TYPE_P;
+        frame->key_frame = 0;
+        memcpy(frame->data[1], hnm->palette, 256 * 4);
+        *got_frame = 1;
+        hnm_flip_buffers(hnm);
+    } else {
+        av_log(avctx, AV_LOG_ERROR, "invalid chunk id: %d\n", chunk_id);
+        return AVERROR_INVALIDDATA;
+    }
+
+    return avpkt->size;
+}
+
+static av_cold int hnm_decode_init(AVCodecContext *avctx)
+{
+    Hnm4VideoContext *hnm = avctx->priv_data;
+
+    if (avctx->extradata_size < 1) {
+        av_log(avctx, AV_LOG_ERROR,
+               "Extradata missing, decoder requires version number\n");
+        return AVERROR_INVALIDDATA;
+    }
+
+    hnm->version   = avctx->extradata[0];
+    avctx->pix_fmt = AV_PIX_FMT_PAL8;
+    hnm->width     = avctx->width;
+    hnm->height    = avctx->height;
+    hnm->buffer1   = av_mallocz(avctx->width * avctx->height);
+    hnm->buffer2   = av_mallocz(avctx->width * avctx->height);
+    hnm->processed = av_mallocz(avctx->width * avctx->height);
+
+    if (!hnm->buffer1 || !hnm->buffer2 || !hnm->processed) {
+        av_log(avctx, AV_LOG_ERROR, "av_mallocz() failed\n");
+        av_freep(&hnm->buffer1);
+        av_freep(&hnm->buffer2);
+        av_freep(&hnm->processed);
+        return AVERROR(ENOMEM);
+    }
+
+    hnm->current  = hnm->buffer1;
+    hnm->previous = hnm->buffer2;
+
+    return 0;
+}
+
+static av_cold int hnm_decode_end(AVCodecContext *avctx)
+{
+    Hnm4VideoContext *hnm = avctx->priv_data;
+
+    av_freep(&hnm->buffer1);
+    av_freep(&hnm->buffer2);
+    av_freep(&hnm->processed);
+
+    return 0;
+}
+
+AVCodec ff_hnm4_video_decoder = {
+    .name           = "hnm4video",
+    .long_name      = NULL_IF_CONFIG_SMALL("HNM 4 video"),
+    .type           = AVMEDIA_TYPE_VIDEO,
+    .id             = AV_CODEC_ID_HNM4_VIDEO,
+    .priv_data_size = sizeof(Hnm4VideoContext),
+    .init           = hnm_decode_init,
+    .close          = hnm_decode_end,
+    .decode         = hnm_decode_frame,
+    .capabilities   = CODEC_CAP_DR1,
+};
diff --git a/libavcodec/version.h b/libavcodec/version.h
index e394c76..838587f 100644
--- a/libavcodec/version.h
+++ b/libavcodec/version.h
@@ -27,7 +27,7 @@
  */
 
 #define LIBAVCODEC_VERSION_MAJOR 55
-#define LIBAVCODEC_VERSION_MINOR 22
+#define LIBAVCODEC_VERSION_MINOR 23
 #define LIBAVCODEC_VERSION_MICRO  0
 
 #define LIBAVCODEC_VERSION_INT  AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \
diff --git a/libavformat/Makefile b/libavformat/Makefile
index 9bc9cdf..d474784 100644
--- a/libavformat/Makefile
+++ b/libavformat/Makefile
@@ -142,6 +142,7 @@ OBJS-$(CONFIG_H264_DEMUXER)              += h264dec.o rawdec.o
 OBJS-$(CONFIG_H264_MUXER)                += rawenc.o
 OBJS-$(CONFIG_HLS_DEMUXER)               += hls.o
 OBJS-$(CONFIG_HLS_MUXER)                 += hlsenc.o
+OBJS-$(CONFIG_HNM_DEMUXER)               += hnm.o
 OBJS-$(CONFIG_IDCIN_DEMUXER)             += idcin.o
 OBJS-$(CONFIG_IFF_DEMUXER)               += iff.o
 OBJS-$(CONFIG_ILBC_DEMUXER)              += ilbc.o
diff --git a/libavformat/allformats.c b/libavformat/allformats.c
index fe5f582..a695815 100644
--- a/libavformat/allformats.c
+++ b/libavformat/allformats.c
@@ -118,6 +118,7 @@ void av_register_all(void)
     REGISTER_MUXDEMUX(H263,             h263);
     REGISTER_MUXDEMUX(H264,             h264);
     REGISTER_MUXDEMUX(HLS,              hls);
+    REGISTER_DEMUXER (HNM,              hnm);
     REGISTER_DEMUXER (IDCIN,            idcin);
     REGISTER_DEMUXER (IFF,              iff);
     REGISTER_MUXDEMUX(ILBC,             ilbc);
diff --git a/libavformat/hnm.c b/libavformat/hnm.c
new file mode 100644
index 0000000..ee34a14
--- /dev/null
+++ b/libavformat/hnm.c
@@ -0,0 +1,204 @@
+/*
+ * Cryo Interactive Entertainment HNM4 demuxer
+ *
+ * Copyright (c) 2012 David Kment
+ *
+ * This file is part of Libav.
+ *
+ * Libav 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.
+ *
+ * Libav 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 Libav; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "libavutil/intreadwrite.h"
+#include "avformat.h"
+#include "internal.h"
+
+#define HNM4_TAG MKTAG('H', 'N', 'M', '4')
+
+#define HNM4_SAMPLE_RATE 22050
+#define HNM4_FRAME_FPS 24
+
+#define HNM4_CHUNK_ID_PL 19536
+#define HNM4_CHUNK_ID_IZ 23113
+#define HNM4_CHUNK_ID_IU 21833
+#define HNM4_CHUNK_ID_SD 17491
+
+typedef struct Hnm4DemuxContext {
+    uint8_t version;
+    uint16_t width;
+    uint16_t height;
+    uint32_t filesize;
+    uint32_t frames;
+    uint32_t taboffset;
+    uint16_t bits;
+    uint16_t channels;
+    uint32_t framesize;
+    uint32_t currentframe;
+    int64_t pts;
+    uint32_t superchunk_remaining;
+    AVPacket vpkt;
+} Hnm4DemuxContext;
+
+static int hnm_probe(AVProbeData *p)
+{
+    if (p->buf_size < 4)
+        return 0;
+
+    // check for HNM4 header.
+    // currently only HNM v4/v4A is supported
+    if (AV_RL32(&p->buf[0]) == HNM4_TAG)
+        return AVPROBE_SCORE_MAX;
+
+    return 0;
+}
+
+static int hnm_read_header(AVFormatContext *s)
+{
+    Hnm4DemuxContext *hnm = s->priv_data;
+    AVIOContext *pb = s->pb;
+    AVStream *vst;
+
+    /* default context members */
+    hnm->pts = 0;
+    av_init_packet(&hnm->vpkt);
+    hnm->vpkt.data = NULL;
+    hnm->vpkt.size = 0;
+
+    hnm->superchunk_remaining = 0;
+
+    avio_skip(pb, 8);
+    hnm->width     = avio_rl16(pb);
+    hnm->height    = avio_rl16(pb);
+    hnm->filesize  = avio_rl32(pb);
+    hnm->frames    = avio_rl32(pb);
+    hnm->taboffset = avio_rl32(pb);
+    hnm->bits      = avio_rl16(pb);
+    hnm->channels  = avio_rl16(pb);
+    hnm->framesize = avio_rl32(pb);
+    avio_skip(pb, 32);
+
+    hnm->currentframe = 0;
+
+    if (hnm->width  < 320 || hnm->width  > 640 ||
+        hnm->height < 150 || hnm->height > 480) {
+        av_log(s, AV_LOG_ERROR,
+               "invalid resolution: %ux%u\n", hnm->width, hnm->height);
+        return AVERROR_INVALIDDATA;
+    }
+
+    // TODO: find a better way to detect HNM4A
+    if (hnm->width == 640)
+        hnm->version = 0x4a;
+    else
+        hnm->version = 0x40;
+
+    if (!(vst = avformat_new_stream(s, NULL)))
+        return AVERROR(ENOMEM);
+
+    vst->codec->codec_type = AVMEDIA_TYPE_VIDEO;
+    vst->codec->codec_id   = AV_CODEC_ID_HNM4_VIDEO;
+    vst->codec->codec_tag  = 0;
+    vst->codec->width      = hnm->width;
+    vst->codec->height     = hnm->height;
+    vst->codec->extradata  = av_mallocz(1);
+
+    vst->codec->extradata_size = 1;
+    memcpy(vst->codec->extradata, &hnm->version, 1);
+
+    vst->start_time = 0;
+
+    avpriv_set_pts_info(vst, 33, 1, HNM4_FRAME_FPS);
+
+    return 0;
+}
+
+static int hnm_read_packet(AVFormatContext *s, AVPacket *pkt)
+{
+    Hnm4DemuxContext *hnm = s->priv_data;
+    AVIOContext *pb = s->pb;
+    int ret = 0;
+
+    uint32_t superchunk_size, chunk_size;
+    uint16_t chunk_id;
+
+    if (hnm->currentframe == hnm->frames || pb->eof_reached)
+        return AVERROR_EOF;
+
+    if (hnm->superchunk_remaining == 0) {
+        /* parse next superchunk */
+        superchunk_size = avio_rl24(pb);
+        avio_skip(pb, 1);
+
+        hnm->superchunk_remaining = superchunk_size - 4;
+    }
+
+    chunk_size = avio_rl24(pb);
+    avio_skip(pb, 1);
+    chunk_id = avio_rl16(pb);
+    avio_skip(pb, 2);
+
+    if (chunk_size > hnm->superchunk_remaining) {
+        av_log(s, AV_LOG_ERROR, "invalid chunk size: %u, offset: %u\n",
+               chunk_size, (int) avio_tell(pb));
+        avio_skip(pb, hnm->superchunk_remaining - 8);
+        hnm->superchunk_remaining = 0;
+    }
+
+    switch (chunk_id) {
+    case HNM4_CHUNK_ID_PL:
+    case HNM4_CHUNK_ID_IZ:
+    case HNM4_CHUNK_ID_IU:
+        avio_seek(pb, -8, SEEK_CUR);
+        ret += av_get_packet(pb, pkt, chunk_size);
+        hnm->superchunk_remaining -= chunk_size;
+        if (chunk_id == HNM4_CHUNK_ID_IZ || chunk_id == HNM4_CHUNK_ID_IU)
+            hnm->currentframe++;
+        break;
+
+    case HNM4_CHUNK_ID_SD:
+        avio_skip(pb, chunk_size - 8);
+        hnm->superchunk_remaining -= chunk_size;
+        break;
+
+    default:
+        av_log(s, AV_LOG_WARNING, "unknown chunk found: %d, offset: %d\n",
+               chunk_id, (int) avio_tell(pb));
+        avio_skip(pb, chunk_size - 8);
+        hnm->superchunk_remaining -= chunk_size;
+        break;
+    }
+
+    return ret;
+}
+
+static int hnm_read_close(AVFormatContext *s)
+{
+    Hnm4DemuxContext *hnm = s->priv_data;
+
+    if (hnm->vpkt.size > 0)
+        av_free_packet(&hnm->vpkt);
+
+    return 0;
+}
+
+AVInputFormat ff_hnm_demuxer = {
+    .name           = "hnm",
+    .long_name      = NULL_IF_CONFIG_SMALL("Cryo HNM v4"),
+    .priv_data_size = sizeof(Hnm4DemuxContext),
+    .read_probe     = hnm_probe,
+    .read_header    = hnm_read_header,
+    .read_packet    = hnm_read_packet,
+    .read_close     = hnm_read_close,
+    .flags          = AVFMT_NO_BYTE_SEEK | AVFMT_NOGENSEARCH | AVFMT_NOBINSEARCH
+};
diff --git a/libavformat/version.h b/libavformat/version.h
index 791933f..0293225 100644
--- a/libavformat/version.h
+++ b/libavformat/version.h
@@ -30,7 +30,7 @@
 #include "libavutil/avutil.h"
 
 #define LIBAVFORMAT_VERSION_MAJOR 55
-#define LIBAVFORMAT_VERSION_MINOR  7
+#define LIBAVFORMAT_VERSION_MINOR  8
 #define LIBAVFORMAT_VERSION_MICRO  0
 
 #define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \



More information about the ffmpeg-cvslog mailing list