Skip to content

Commit

Permalink
feat: add flv file parser
Browse files Browse the repository at this point in the history
  • Loading branch information
ireader committed Jun 12, 2021
1 parent 808e956 commit 9383799
Show file tree
Hide file tree
Showing 8 changed files with 307 additions and 37 deletions.
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
*.ipch
*.obj
Debug
.vs
x64
Release
*.user
*.d
*.o
2 changes: 1 addition & 1 deletion libdash/test/dash-dynamic-test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ static int dash_live_worker(const char* file, dash_playlist_t* dash)

timestamp += diff;
s_timestamp = timestamp > s_timestamp ? timestamp : s_timestamp;
r = flv_parser_input(type, dash->packet, taglen, timestamp, dash_live_onflv, dash);
r = flv_parser_tag(type, dash->packet, taglen, timestamp, dash_live_onflv, dash);
if (0 != r)
{
assert(0);
Expand Down
22 changes: 21 additions & 1 deletion libflv/include/flv-parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include <stdint.h>
#include <stddef.h>
#include "flv-header.h"

#if defined(__cplusplus)
extern "C" {
Expand All @@ -25,7 +26,26 @@ typedef int (*flv_parser_handler)(void* param, int codec, const void* data, size
/// @param[in] bytes data length in byte
/// @param[in] timestamp milliseconds relative to the first tag(DTS)
/// @return 0-ok, other-error
int flv_parser_input(int type, const void* data, size_t bytes, uint32_t timestamp, flv_parser_handler handler, void* param);
int flv_parser_tag(int type, const void* data, size_t bytes, uint32_t timestamp, flv_parser_handler handler, void* param);

struct flv_parser_t
{
int state;

int bytes;
int expect;
uint8_t ptr[32];
struct flv_header_t header;
struct flv_tag_header_t tag;
struct flv_audio_tag_header_t audio;
struct flv_video_tag_header_t video;

uint8_t* body;
void* (*alloc)(void* param, size_t bytes);
void (*free)(void* param, void* ptr);
};

int flv_parser_input(struct flv_parser_t* parser, const uint8_t* data, int bytes, flv_parser_handler handler, void* param);

#if defined(__cplusplus)
}
Expand Down
224 changes: 192 additions & 32 deletions libflv/source/flv-parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,40 +6,33 @@
#include <assert.h>
#include <errno.h>

#define N_TAG_SIZE 4 // previous tag size
#define FLV_HEADER_SIZE 9 // DataOffset included
#define FLV_TAG_HEADER_SIZE 11 // StreamID included

#define FLV_VIDEO_CODEC_NAME(codecid) (FLV_VIDEO_H264==(codecid) ? FLV_VIDEO_AVCC : (FLV_VIDEO_H265==(codecid) ? FLV_VIDEO_HVCC : FLV_VIDEO_AV1C))

static int flv_parser_audio(const uint8_t* data, int bytes, uint32_t timestamp, flv_parser_handler handler, void* param)
static int flv_parser_audio(struct flv_audio_tag_header_t* audio, const uint8_t* data, int bytes, uint32_t timestamp, flv_parser_handler handler, void* param)
{
int n;
struct flv_audio_tag_header_t audio;
n = flv_audio_tag_header_read(&audio, data, bytes);
if (n < 0)
return n;
if (FLV_SEQUENCE_HEADER == audio.avpacket)
return handler(param, FLV_AUDIO_AAC == audio.codecid ? FLV_AUDIO_ASC : FLV_AUDIO_OPUS_HEAD, data + n, bytes - n, timestamp, timestamp, 0);
if (FLV_SEQUENCE_HEADER == audio->avpacket)
return handler(param, FLV_AUDIO_AAC == audio->codecid ? FLV_AUDIO_ASC : FLV_AUDIO_OPUS_HEAD, data, bytes, timestamp, timestamp, 0);
else
return handler(param, audio.codecid, data + n, bytes - n, timestamp, timestamp, 0);
return handler(param, audio->codecid, data, bytes, timestamp, timestamp, 0);
}

static int flv_parser_video(const uint8_t* data, int bytes, uint32_t timestamp, flv_parser_handler handler, void* param)
static int flv_parser_video(struct flv_video_tag_header_t* video, const uint8_t* data, int bytes, uint32_t timestamp, flv_parser_handler handler, void* param)
{
int n;
struct flv_video_tag_header_t video;
n = flv_video_tag_header_read(&video, data, bytes);
if (n < 0)
return n;

if (FLV_VIDEO_H264 == video.codecid || FLV_VIDEO_H265 == video.codecid || FLV_VIDEO_AV1 == video.codecid)
if (FLV_VIDEO_H264 == video->codecid || FLV_VIDEO_H265 == video->codecid || FLV_VIDEO_AV1 == video->codecid)
{
if (FLV_SEQUENCE_HEADER == video.avpacket)
if (FLV_SEQUENCE_HEADER == video->avpacket)
{
return handler(param, FLV_VIDEO_CODEC_NAME(video.codecid), data + n, bytes - n, timestamp, timestamp, 0);
return handler(param, FLV_VIDEO_CODEC_NAME(video->codecid), data, bytes, timestamp, timestamp, 0);
}
else if (FLV_AVPACKET == video.avpacket)
else if (FLV_AVPACKET == video->avpacket)
{
return handler(param, video.codecid, data + n, bytes - n, timestamp + video.cts, timestamp, (FLV_VIDEO_KEY_FRAME == video.keyframe) ? 1 : 0);
return handler(param, video->codecid, data, bytes, timestamp + video->cts, timestamp, (FLV_VIDEO_KEY_FRAME == video->keyframe) ? 1 : 0);
}
else if (FLV_END_OF_SEQUENCE == video.avpacket)
else if (FLV_END_OF_SEQUENCE == video->avpacket)
{
return 0; // AVC end of sequence (lower level NALU sequence ender is not required or supported)
}
Expand All @@ -52,38 +45,205 @@ static int flv_parser_video(const uint8_t* data, int bytes, uint32_t timestamp,
else
{
// Video frame data
return handler(param, video.codecid, data + n, bytes - n, timestamp, timestamp, (FLV_VIDEO_KEY_FRAME == video.keyframe) ? 1 : 0);
return handler(param, video->codecid, data, bytes, timestamp, timestamp, (FLV_VIDEO_KEY_FRAME == video->keyframe) ? 1 : 0);
}
}

// http://www.cnblogs.com/musicfans/archive/2012/11/07/2819291.html
// metadata keyframes/filepositions
static int flv_parser_script(const uint8_t* data, int bytes, uint32_t timestamp, flv_parser_handler handler, void* param)
{
int n;
n = flv_data_tag_header_read(data, bytes);
if (n < 0)
return n;
return handler(param, 0, data + n, bytes - n, timestamp, timestamp, 0);
return handler(param, FLV_SCRIPT_METADATA, data, bytes, timestamp, timestamp, 0);
}

int flv_parser_input(int type, const void* data, size_t bytes, uint32_t timestamp, flv_parser_handler handler, void* param)
int flv_parser_tag(int type, const void* data, size_t bytes, uint32_t timestamp, flv_parser_handler handler, void* param)
{
int n;
struct flv_audio_tag_header_t audio;
struct flv_video_tag_header_t video;

if (bytes < 1) return -EINVAL;

switch (type)
{
case FLV_TYPE_AUDIO:
return flv_parser_audio(data, (int)bytes, timestamp, handler, param);
n = flv_audio_tag_header_read(&audio, data, bytes);
if (n < 0)
return n;
return flv_parser_audio(&audio, (const uint8_t*)data + n, (int)bytes - n, timestamp, handler, param);

case FLV_TYPE_VIDEO:
return flv_parser_video(data, (int)bytes, timestamp, handler, param);
n = flv_video_tag_header_read(&video, data, bytes);
if (n < 0)
return n;
return flv_parser_video(&video, (const uint8_t*)data + n, (int)bytes - n, timestamp, handler, param);

case FLV_TYPE_SCRIPT:
return flv_parser_script(data, (int)bytes, timestamp, handler, param);
n = flv_data_tag_header_read(data, bytes);
if (n < 0)
return n;
return flv_parser_script((const uint8_t*)data + n, (int)bytes - n, timestamp, handler, param);

default:
assert(0);
return -1;
}
}

static int flv_parser_append(struct flv_parser_t* parser, const uint8_t* data, int bytes, int expect)
{
int n;
assert(parser->bytes <= expect && expect <= sizeof(parser->ptr));
n = parser->bytes + bytes >= expect ? expect - parser->bytes : bytes;
if (n > 0)
{
memcpy(parser->ptr + parser->bytes, data, n);
parser->bytes += n;
}
return n;
}

int flv_parser_input(struct flv_parser_t* parser, const uint8_t* data, int bytes, flv_parser_handler handler, void* param)
{
int n, r;
uint8_t codec;
uint32_t size;
enum {FLV_HEADER=0, FLV_HEADER_OFFSET, FLV_PREVIOUS_SIZE, FLV_TAG_HEADER, FLV_AVHEADER_CODEC, FLV_AVHEADER_EXTRA, FLV_TAG_BODY};

for (n = 0; bytes > 0 && n >= 0; data += n, bytes -= n)
{
switch (parser->state)
{
case FLV_HEADER:
n = flv_parser_append(parser, data, bytes, FLV_HEADER_SIZE);
if (FLV_HEADER_SIZE == parser->bytes)
{
flv_header_read(&parser->header, parser->ptr, parser->bytes);
if (parser->header.offset < 9 || parser->header.offset > sizeof(parser->ptr))
return -1;
parser->header.offset -= 9;
parser->state = parser->header.offset > 0 ? FLV_HEADER_OFFSET : FLV_PREVIOUS_SIZE;
parser->bytes = 0;
}
break;

case FLV_HEADER_OFFSET:
n = flv_parser_append(parser, data, bytes, parser->header.offset);
if (parser->header.offset == (uint32_t)parser->bytes)
{
parser->bytes = 0;
parser->state = FLV_PREVIOUS_SIZE;
}
break;

case FLV_PREVIOUS_SIZE:
n = flv_parser_append(parser, data, bytes, N_TAG_SIZE);
if (N_TAG_SIZE == parser->bytes)
{
flv_tag_size_read(parser->ptr, parser->bytes, &size);
assert(size == 0 || size == parser->tag.size + FLV_TAG_HEADER_SIZE);
parser->bytes = 0;
parser->state = FLV_TAG_HEADER;
}
break;

case FLV_TAG_HEADER:
n = flv_parser_append(parser, data, bytes, FLV_TAG_HEADER_SIZE);
if (FLV_TAG_HEADER_SIZE == parser->bytes)
{
flv_tag_header_read(&parser->tag, parser->ptr, parser->bytes);
parser->bytes = 0;
parser->state = FLV_AVHEADER_CODEC;
}
break;

case FLV_AVHEADER_CODEC:
switch (parser->tag.type)
{
case FLV_TYPE_AUDIO:
parser->expect = 1;
n = flv_parser_append(parser, data, bytes, 1);
codec = (parser->ptr[0] & 0xF0) /*>> 4*/;
if (FLV_AUDIO_AAC == codec || FLV_AUDIO_OPUS == codec)
parser->expect = 2;
break;

case FLV_TYPE_VIDEO:
parser->expect = 1;
n = flv_parser_append(parser, data, bytes, 1);
codec = (parser->ptr[0] & 0x0F);
if (FLV_VIDEO_H264 == codec || FLV_VIDEO_H265 == codec || FLV_VIDEO_AV1 == codec)
parser->expect = 5;
break;

case FLV_TYPE_SCRIPT:
parser->expect = 0;
n = 0; // noops
}
parser->state = FLV_AVHEADER_EXTRA;
break;

case FLV_AVHEADER_EXTRA:
n = flv_parser_append(parser, data, bytes, parser->expect);
if (parser->expect == parser->bytes)
{
if(FLV_TYPE_AUDIO == parser->tag.type)
flv_audio_tag_header_read(&parser->audio, parser->ptr, parser->bytes);
else if(FLV_TYPE_VIDEO == parser->tag.type)
flv_video_tag_header_read(&parser->video, parser->ptr, parser->bytes);
parser->bytes = 0;
parser->state = FLV_TAG_BODY;

parser->expect = parser->tag.size - parser->expect;
parser->body = parser->alloc ? parser->alloc(param, parser->expect) : malloc(parser->expect);
if (!parser->body)
return -1;
}
break;

case FLV_TAG_BODY:
assert(parser->body && parser->bytes <= parser->expect);
n = parser->bytes + bytes >= parser->expect ? parser->expect - parser->bytes : bytes;
if(n > 0) {
memmove(parser->body + parser->bytes, data, n);
parser->bytes += n;
}

if (parser->expect == parser->bytes)
{
parser->bytes = 0;
parser->state = FLV_PREVIOUS_SIZE;
switch (parser->tag.type)
{
case FLV_TYPE_AUDIO:
r = flv_parser_audio(&parser->audio, parser->body, parser->expect, parser->tag.timestamp, handler, param);
break;

case FLV_TYPE_VIDEO:
r = flv_parser_video(&parser->video, parser->body, parser->expect, parser->tag.timestamp, handler, param);
break;

case FLV_TYPE_SCRIPT:
r = flv_parser_script(parser->body, parser->expect, parser->tag.timestamp, handler, param);
break;

default:
assert(0);
r = -1;
break;
}

parser->free ? parser->free(param, parser->body) : free(parser->body);
if (0 != r)
return r;
}
break;

default:
assert(0);
return -1;
}
}

return 0;
}
Loading

0 comments on commit 9383799

Please sign in to comment.