[FFmpeg-devel] [PATCH v3] avformat/ifv: added support for ifv cctv files

Paul B Mahol onemda at gmail.com
Fri May 24 11:59:39 EEST 2019


On 5/15/19, Swaraj Hota <swarajhota353 at gmail.com> wrote:
> Fixes ticket #2956.
>
> Signed-off-by: Swaraj Hota <swarajhota353 at gmail.com>
> ---
> Revised patch based on previous discussions.
> Some of the changes are:
> - using AVIndexEntry now
> - demuxer is totally index based (removed linear search)
> - added seeking functionality with timestamps
>
> There are some timing issues though, due to which seeking does not
> work in the files with audio (works fine for files without audio).
> I tried a lot but couldn't figure it out, maybe I don't understand
> timing stuff clearly. Any suggestions regarding this will be really
> helpful. Thanks in advance!
>
> ---
>  Changelog                |   1 +
>  libavformat/Makefile     |   1 +
>  libavformat/allformats.c |   1 +
>  libavformat/ifv.c        | 316 +++++++++++++++++++++++++++++++++++++++
>  libavformat/version.h    |   4 +-
>  5 files changed, 321 insertions(+), 2 deletions(-)
>  create mode 100644 libavformat/ifv.c
>
> diff --git a/Changelog b/Changelog
> index e6b209ae0a..e0b27657d7 100644
> --- a/Changelog
> +++ b/Changelog
> @@ -30,6 +30,7 @@ version <next>:
>  - colorhold filter
>  - xmedian filter
>  - asr filter
> +- IFV demuxer
>
>
>  version 4.1:
> diff --git a/libavformat/Makefile b/libavformat/Makefile
> index 99be60d184..f68d41e4a5 100644
> --- a/libavformat/Makefile
> +++ b/libavformat/Makefile
> @@ -231,6 +231,7 @@ OBJS-$(CONFIG_ICO_MUXER)                 += icoenc.o
>  OBJS-$(CONFIG_IDCIN_DEMUXER)             += idcin.o
>  OBJS-$(CONFIG_IDF_DEMUXER)               += bintext.o sauce.o
>  OBJS-$(CONFIG_IFF_DEMUXER)               += iff.o
> +OBJS-$(CONFIG_IFV_DEMUXER)               += ifv.o
>  OBJS-$(CONFIG_ILBC_DEMUXER)              += ilbc.o
>  OBJS-$(CONFIG_ILBC_MUXER)                += ilbc.o
>  OBJS-$(CONFIG_IMAGE2_DEMUXER)            += img2dec.o img2.o
> diff --git a/libavformat/allformats.c b/libavformat/allformats.c
> index d316a0529a..cd00834807 100644
> --- a/libavformat/allformats.c
> +++ b/libavformat/allformats.c
> @@ -188,6 +188,7 @@ extern AVOutputFormat ff_ico_muxer;
>  extern AVInputFormat  ff_idcin_demuxer;
>  extern AVInputFormat  ff_idf_demuxer;
>  extern AVInputFormat  ff_iff_demuxer;
> +extern AVInputFormat  ff_ifv_demuxer;
>  extern AVInputFormat  ff_ilbc_demuxer;
>  extern AVOutputFormat ff_ilbc_muxer;
>  extern AVInputFormat  ff_image2_demuxer;
> diff --git a/libavformat/ifv.c b/libavformat/ifv.c
> new file mode 100644
> index 0000000000..c834b3b63c
> --- /dev/null
> +++ b/libavformat/ifv.c
> @@ -0,0 +1,316 @@
> +/*
> + * IFV demuxer
> + *
> + * Copyright (c) 2019 Swaraj Hota
> + *
> + * 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
> + */
> +
> +#include "avformat.h"
> +#include "internal.h"
> +#include "avio_internal.h"
> +
> +
> +typedef struct IFVContext {
> +    uint32_t next_video_index;
> +    uint32_t next_audio_index;
> +    uint32_t total_vframes;
> +    uint32_t total_aframes;
> +
> +    int width, height;
> +    int is_audio_present;
> +    int sample_rate;
> +
> +    int video_stream_index;
> +    int audio_stream_index;
> +} IFVContext;
> +
> +static int ifv_probe(const AVProbeData *p)
> +{
> +    static const uint8_t ifv_magic[] = {0x11, 0xd2, 0xd3, 0xab, 0xba, 0xa9,
> +        0xcf, 0x11, 0x8e, 0xe6, 0x00, 0xc0, 0x0c, 0x20, 0x53, 0x65, 0x44};
> +
> +    if (!memcmp(p->buf, ifv_magic, sizeof(ifv_magic)))
> +        return AVPROBE_SCORE_MAX;
> +
> +    return 0;
> +}
> +
> +static int read_index(AVFormatContext *s,
> +                      enum AVMediaType frame_type,
> +                      uint32_t start_index)
> +{
> +    IFVContext *ifv = s->priv_data;
> +    AVStream *st;
> +    int64_t pos, size, timestamp;
> +    uint32_t end_index, i;
> +    int ret;
> +
> +    if (frame_type == AVMEDIA_TYPE_VIDEO) {
> +        end_index = ifv->total_vframes;
> +        st = s->streams[ifv->video_stream_index];
> +    } else {
> +        end_index = ifv->total_aframes;
> +        st = s->streams[ifv->audio_stream_index];
> +    }
> +
> +    for (i = start_index; i < end_index; i++) {
> +        pos = avio_rl32(s->pb);
> +        size = avio_rl32(s->pb);
> +
> +        avio_skip(s->pb, 8);
> +        timestamp = avio_rl32(s->pb);

You sure about this?

Doing real reverse engineering of relevant application would show how
timestamps are handled.

> +
> +        ret = av_add_index_entry(st, pos, timestamp, size, 0, 0);
> +        if (ret < 0)
> +            return ret;
> +
> +        avio_skip(s->pb, frame_type == AVMEDIA_TYPE_VIDEO? 8: 4);
> +    }
> +
> +    return 0;
> +}
> +
> +static int parse_header(AVFormatContext *s)
> +{
> +    IFVContext *ifv = s->priv_data;
> +    uint32_t aud_magic;
> +    uint32_t vid_magic;
> +
> +    avio_skip(s->pb, 0x5c);
> +    ifv->width = avio_rl16(s->pb);
> +    ifv->height = avio_rl16(s->pb);
> +
> +    avio_skip(s->pb, 0x8);
> +    vid_magic = avio_rl32(s->pb);
> +
> +    if (vid_magic != MKTAG('H','2','6','4'))
> +        avpriv_request_sample(s, "Unknown video codec %x\n", vid_magic);
> +
> +    avio_skip(s->pb, 0x2c);
> +    ifv->sample_rate = avio_rl32(s->pb);
> +    aud_magic = avio_rl32(s->pb);
> +
> +    if (aud_magic == MKTAG('G','R','A','W')) {
> +        ifv->is_audio_present = 1;
> +    } else if (aud_magic == MKTAG('P','C','M','U')) {
> +        ifv->is_audio_present = 0;
> +    } else {
> +        avpriv_request_sample(s, "Unknown audio codec %x\n", aud_magic);
> +    }
> +
> +    avio_skip(s->pb, 0x44);
> +    ifv->total_vframes = avio_rl32(s->pb);
> +    ifv->total_aframes = avio_rl32(s->pb);
> +
> +    return 0;
> +}
> +
> +static int ifv_read_header(AVFormatContext *s)
> +{
> +    IFVContext *ifv = s->priv_data;
> +    AVStream *st;
> +    int ret;
> +
> +    ret = parse_header(s);
> +    if (ret < 0)
> +        return ret;
> +
> +    st = avformat_new_stream(s, NULL);
> +    if (!st)
> +        return AVERROR(ENOMEM);
> +
> +    st->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
> +    st->codecpar->codec_id = AV_CODEC_ID_H264;
> +    st->codecpar->width = ifv->width;
> +    st->codecpar->height = ifv->height;
> +    st->start_time = 0;
> +    ifv->video_stream_index = st->index;
> +
> +    avpriv_set_pts_info(st, 32, 1, 25);

Inspecting timestamps this code returns it looks like video "pts" are
not in this timebase unit.

> +
> +    if (ifv->is_audio_present) {
> +        st = avformat_new_stream(s, NULL);
> +        if (!st)
> +            return AVERROR(ENOMEM);
> +
> +        st->codecpar->codec_type = AVMEDIA_TYPE_AUDIO;
> +        st->codecpar->codec_id = AV_CODEC_ID_PCM_S16LE;
> +        st->codecpar->channels = 1;
> +        st->codecpar->channel_layout = AV_CH_LAYOUT_MONO;
> +        st->codecpar->sample_rate = ifv->sample_rate;
> +        ifv->audio_stream_index = st->index;
> +
> +        avpriv_set_pts_info(st, 32, 1, 25);

I'm not sure audio pts are in this timebase unit.

> +    }
> +
> +    /*read video index*/
> +    avio_seek(s->pb, 0xf8, SEEK_SET);
> +
> +    ret = read_index(s, AVMEDIA_TYPE_VIDEO, 0);
> +    if (ret < 0)
> +        return ret;
> +
> +    if (ifv->is_audio_present) {
> +        /*read audio index*/
> +        avio_seek(s->pb, 0x14918, SEEK_SET);
> +
> +        ret = read_index(s, AVMEDIA_TYPE_AUDIO, 0);
> +        if (ret < 0)
> +            return ret;
> +    }
> +
> +    ifv->next_video_index = 0;
> +    ifv->next_audio_index = 0;
> +
> +    return 0;
> +}
> +
> +static int ifv_read_packet(AVFormatContext *s, AVPacket *pkt)
> +{
> +    IFVContext *ifv = s->priv_data;
> +    AVStream *st;
> +    AVIndexEntry *ev, *ea, *e_next;
> +    uint32_t nb_new_vframes, nb_new_aframes;
> +    int ret;
> +
> +    ev = ea = e_next = NULL;
> +
> +    if (ifv->next_video_index < ifv->total_vframes) {
> +        st = s->streams[ifv->video_stream_index];
> +        if (ifv->next_video_index < st->nb_index_entries)
> +            e_next = ev = &st->index_entries[ifv->next_video_index];
> +    }
> +
> +    if (ifv->is_audio_present &&
> +        ifv->next_audio_index < ifv->total_aframes) {
> +        st = s->streams[ifv->audio_stream_index];
> +        if (ifv->next_audio_index < st->nb_index_entries) {
> +            ea = &st->index_entries[ifv->next_audio_index];
> +            if (!ev || ea->timestamp < ev->timestamp)
> +                e_next = ea;
> +        }
> +    }
> +
> +    if (!ev) {
> +        if (ifv->is_audio_present && !ea) {
> +            /*read new video and audio indexes*/
> +
> +            avio_skip(s->pb, 0x1c);
> +            nb_new_vframes = avio_rl32(s->pb);
> +            nb_new_aframes = avio_rl32(s->pb);
> +            avio_skip(s->pb, 0xc);
> +
> +            if (avio_feof(s->pb))
> +                return AVERROR_EOF;
> +
> +            ifv->next_video_index = ifv->total_vframes;
> +            ifv->total_vframes += nb_new_vframes;
> +
> +            ret = read_index(s, AVMEDIA_TYPE_VIDEO, ifv->next_video_index);
> +            if (ret < 0)
> +                return ret;
> +
> +            ifv->next_audio_index = ifv->total_aframes;
> +            ifv->total_aframes += nb_new_aframes;
> +
> +            ret = read_index(s, AVMEDIA_TYPE_AUDIO, ifv->next_video_index);

This looks wrong, should it be audio index?

> +            if (ret < 0)
> +                return ret;
> +
> +            return 0;
> +
> +        } else if (!ifv->is_audio_present) {
> +            /*read new video index*/
> +
> +            avio_skip(s->pb, 0x1c);
> +            nb_new_vframes = avio_rl32(s->pb);
> +            avio_skip(s->pb, 0x10);
> +
> +            if (avio_feof(s->pb))
> +                return AVERROR_EOF;
> +
> +            ifv->next_video_index = ifv->total_vframes;
> +            ifv->total_vframes += nb_new_vframes;
> +
> +            ret = read_index(s, AVMEDIA_TYPE_VIDEO, ifv->next_video_index);
> +            if (ret < 0)
> +                return ret;
> +
> +            return 0;
> +        }
> +    }
> +
> +    if (!e_next) return AVERROR_EOF;
> +
> +    avio_seek(s->pb, e_next->pos, SEEK_SET);
> +    ret = av_get_packet(s->pb, pkt, e_next->size);
> +    if (ret < 0)
> +        return ret;
> +
> +    if (e_next == ev) {
> +        ifv->next_video_index++;
> +        pkt->stream_index = ifv->video_stream_index;

You must set packet timestamp and position here.
You have it from generated index.

> +    } else if (e_next == ea) {
> +        ifv->next_audio_index++;
> +        pkt->stream_index = ifv->audio_stream_index;

You must set packet timestamp and position here.
You have it from generated index.

> +    }
> +
> +    return ret;
> +}
> +
> +static int ifv_read_seek(AVFormatContext *s, int stream_index, int64_t ts,
> int flags)
> +{
> +    IFVContext *ifv = s->priv_data;
> +    AVStream *st = s->streams[0];
> +
> +    int index = av_index_search_timestamp(st, ts, AVSEEK_FLAG_ANY);
> +    if (index < 0) {
> +        ifv->next_video_index = ifv->total_vframes - 1;
> +        ifv->next_audio_index = ifv->total_aframes - 1;
> +        return 0;
> +    }
> +
> +    ifv->next_video_index = index;
> +    ifv->next_audio_index = index;
> +
> +    return 0;
> +}
> +
> +static int ifv_read_close(AVFormatContext *s)
> +{
> +    AVStream *st;
> +    unsigned int i;
> +    for (i = 0; i < s->nb_streams; i++) {
> +        st = s->streams[i];
> +        av_freep(&st->index_entries);
> +    }
> +    return 0;
> +}
> +
> +AVInputFormat ff_ifv_demuxer = {
> +    .name           = "ifv",
> +    .long_name      = NULL_IF_CONFIG_SMALL("IFV CCTV DVR"),
> +    .priv_data_size = sizeof(IFVContext),
> +    .extensions     = "ifv",
> +    .read_probe     = ifv_probe,
> +    .read_header    = ifv_read_header,
> +    .read_packet    = ifv_read_packet,
> +    .read_seek      = ifv_read_seek,
> +    .read_close     = ifv_read_close,
> +    .flags          = AVFMT_NOTIMESTAMPS,

This flag is not valid/needed.

> +};
> diff --git a/libavformat/version.h b/libavformat/version.h
> index 150a72e27d..52dd95f5c6 100644
> --- a/libavformat/version.h
> +++ b/libavformat/version.h
> @@ -32,8 +32,8 @@
>  // Major bumping may affect Ticket5467, 5421, 5451(compatibility with
> Chromium)
>  // Also please add any ticket numbers that you believe might be affected
> here
>  #define LIBAVFORMAT_VERSION_MAJOR  58
> -#define LIBAVFORMAT_VERSION_MINOR  27
> -#define LIBAVFORMAT_VERSION_MICRO 103
> +#define LIBAVFORMAT_VERSION_MINOR  28
> +#define LIBAVFORMAT_VERSION_MICRO 100
>
>  #define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \
>                                                 LIBAVFORMAT_VERSION_MINOR, \
> --
> 2.21.0
>
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel at ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request at ffmpeg.org with subject "unsubscribe".


When writing patch that is not final for merge into main tree, please
refrain from changing Changelog or bumping version numbers,
as that will ease testing this patch because it will be applied immediately.


More information about the ffmpeg-devel mailing list