Merge pull request #147 from mheers/feature/timecode

Feature: read timecode from frames
This commit is contained in:
alex
2022-04-18 12:01:20 +04:00
committed by GitHub
2 changed files with 190 additions and 0 deletions

View File

@@ -1,3 +1,4 @@
//go:build go1.12
// +build go1.12
// Format.
@@ -364,6 +365,21 @@ func (ctx *FmtCtx) GetNextPacket() (*Packet, error) {
return pkt, nil
}
func (ctx *FmtCtx) GetNextPacketForStreamIndex(streamIndex int) (*Packet, error) {
for {
packet, err := ctx.GetNextPacket()
if err != nil {
return nil, fmt.Errorf("failed to get next packet: %s", err)
}
if packet.StreamIndex() == streamIndex {
return packet, nil
}
packet.Free()
}
}
func (ctx *FmtCtx) GetNewPackets() chan *Packet {
yield := make(chan *Packet)
@@ -547,6 +563,72 @@ func (ctx *FmtCtx) SeekFrameAt(sec int64, streamIndex int) error {
return nil
}
func (ctx *FmtCtx) SeekFrameAtTimeCode(timecode string, streamIndex int) error {
ist, err := ctx.GetStream(streamIndex)
if err != nil {
return err
}
istCodecCtx := ist.CodecCtx()
istAvgFrameRate := ist.GetAvgFrameRate()
sec := int64(0)
found := false
for !found {
packet, err := ctx.GetNextPacket()
if err != nil {
return err
}
if packet.StreamIndex() != streamIndex {
continue
}
found, err = isPacketLaterThanTimeCode(packet, timecode, istCodecCtx, istAvgFrameRate)
if err != nil {
return err
}
if !found {
sec += 5
err = ctx.SeekFrameAt(sec, streamIndex)
if err != nil {
return err
}
}
packet.Free()
}
return nil
}
func isPacketLaterThanTimeCode(packet *Packet, timecode string, codecCtx *CodecCtx, avgFrameRate AVRational) (bool, error) {
frameTimeCode, err := getPacketTimeCode(packet, codecCtx, avgFrameRate)
if err != nil {
return false, err
}
if frameTimeCode >= timecode {
return true, nil
}
return false, nil
}
func getPacketTimeCode(packet *Packet, codecCtx *CodecCtx, avgFrameRate AVRational) (string, error) {
frames, err := codecCtx.Decode(packet)
if err != nil {
return "", err
}
for _, frame := range frames {
timecode, err := frame.GetTimeCode(avgFrameRate)
if err != nil {
return "", err
}
frame.Free()
return timecode, nil
}
return "", nil
}
func (ctx *FmtCtx) SetPb(val *AVIOContext) *FmtCtx {
ctx.avCtx.pb = val.avAVIOContext
ctx.customPb = true

View File

@@ -1,3 +1,4 @@
//go:build go1.12
// +build go1.12
package gmf
@@ -5,10 +6,13 @@ package gmf
/*
#cgo pkg-config: libavcodec libavutil
#include <stdlib.h>
#include "libavcodec/avcodec.h"
#include "libavutil/frame.h"
#include "libavutil/imgutils.h"
#include "libavutil/timestamp.h"
#include "libavutil/timecode.h"
#include "libavutil/common.h"
void gmf_set_frame_data(AVFrame *frame, int idx, int l_size, uint8_t data) {
if(!frame) {
@@ -26,6 +30,15 @@ void gmf_free_data(AVFrame *frame) {
av_freep(&frame->data[0]);
}
int gmf_get_timecode(AVFrameSideData *sd, AVRational avgFrameRate, char *out) {
uint32_t *tc = (uint32_t*)sd->data;
char tcbuf[AV_TIMECODE_STR_SIZE];
av_timecode_make_smpte_tc_string2(tcbuf, avgFrameRate, tc[1], 0, 0);
int n;
n = sprintf(out, "%s", tcbuf);
return n;
}
*/
import "C"
@@ -276,3 +289,98 @@ func (f *Frame) GetRawAudioData(plane int) []byte {
func (f *Frame) Time(timebase AVRational) int {
return int(float64(timebase.AVR().Num) / float64(timebase.AVR().Den) * float64(f.Pts()))
}
var frameSideDataTypes []uint32 = []uint32{
C.AV_FRAME_DATA_PANSCAN,
C.AV_FRAME_DATA_A53_CC,
C.AV_FRAME_DATA_AFD,
C.AV_FRAME_DATA_AUDIO_SERVICE_TYPE,
C.AV_FRAME_DATA_CONTENT_LIGHT_LEVEL,
C.AV_FRAME_DATA_DISPLAYMATRIX,
C.AV_FRAME_DATA_DOWNMIX_INFO,
C.AV_FRAME_DATA_DYNAMIC_HDR_PLUS,
C.AV_FRAME_DATA_FILM_GRAIN_PARAMS,
C.AV_FRAME_DATA_GOP_TIMECODE,
C.AV_FRAME_DATA_ICC_PROFILE,
C.AV_FRAME_DATA_MASTERING_DISPLAY_METADATA,
C.AV_FRAME_DATA_MATRIXENCODING,
C.AV_FRAME_DATA_MOTION_VECTORS,
C.AV_FRAME_DATA_QP_TABLE_DATA,
C.AV_FRAME_DATA_QP_TABLE_PROPERTIES,
C.AV_FRAME_DATA_REGIONS_OF_INTEREST,
C.AV_FRAME_DATA_REPLAYGAIN,
C.AV_FRAME_DATA_S12M_TIMECODE,
C.AV_FRAME_DATA_SEI_UNREGISTERED,
C.AV_FRAME_DATA_SKIP_SAMPLES,
C.AV_FRAME_DATA_SPHERICAL,
C.AV_FRAME_DATA_STEREO3D,
C.AV_FRAME_DATA_VIDEO_ENC_PARAMS,
}
func (f *Frame) GetSideDataTypes() (map[uint32]string, error) {
result := make(map[uint32]string)
// get a pointer to the side data of the frame
for _, sideDataType := range frameSideDataTypes {
sideDataPtr := C.av_frame_get_side_data(f.avFrame, sideDataType)
if sideDataPtr != nil {
// cast the pointer to the side data to a AVFrameSideData struct
sideData := (*C.struct_AVFrameSideData)(unsafe.Pointer(sideDataPtr))
sideDataType := C.GoString(C.av_frame_side_data_name(sideData._type))
result[uint32(sideData._type)] = sideDataType
}
}
return result, nil
}
func (f *Frame) GetUserData() ([]byte, error) {
// get a pointer to the side data of the frame
sideDataPtr := C.av_frame_get_side_data(f.avFrame, C.AV_FRAME_DATA_SEI_UNREGISTERED)
if sideDataPtr == nil {
return nil, errors.New("no user data found")
}
// cast the pointer to the side data to a AVFrameSideData struct
sideData := (*C.struct_AVFrameSideData)(unsafe.Pointer(sideDataPtr))
// gets the bytes from the pointer
data := C.GoBytes(unsafe.Pointer(sideData.data), C.int(sideData.size))
return data, nil
}
func (f *Frame) GetTimeCode(avgFrameRate AVRational) (string, error) {
// get a pointer to the side data of the frame
sideDataPtr := C.av_frame_get_side_data(f.avFrame, C.AV_FRAME_DATA_S12M_TIMECODE)
if sideDataPtr == nil {
return "", errors.New("no timecode data found")
}
// cast the pointer to the side data to a AVFrameSideData struct
sideData := (*C.struct_AVFrameSideData)(unsafe.Pointer(sideDataPtr))
// check if the side data is valid
if sideData._type != C.AV_FRAME_DATA_S12M_TIMECODE || sideData.size != 16 {
return "", errors.New("invalid timecode side data")
}
// prepare a byte slice to hold the timecode
ptr := C.malloc(C.sizeof_char * 1024)
defer C.free(unsafe.Pointer(ptr))
// cast the go avgFrameRate to a C AVRational
afr := (C.struct_AVRational)(avgFrameRate)
// get the timecode by calling the C function and passing the sideData, the AVRational and the pointer to the byte slice to hold the timecode
// returns the length of the timecode in bytes
tcSize := C.gmf_get_timecode(sideData, afr, (*C.char)(ptr))
// gets the bytes from the pointer
tc := C.GoBytes(ptr, tcSize)
tcString := string(tc)
return tcString, nil
}