Skip to content

Commit

Permalink
Now handling audio samples in frame data as well
Browse files Browse the repository at this point in the history
  • Loading branch information
asticode committed Oct 16, 2024
1 parent 2a48086 commit c8b4cbe
Show file tree
Hide file tree
Showing 18 changed files with 378 additions and 215 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Examples are located in the [examples](examples) directory and mirror as much as
|Custom IO Demuxing|[see](examples/custom_io_demuxing/main.go)|[see](https://github.com/FFmpeg/FFmpeg/blob/n7.0/doc/examples/avio_reading.c)
|Demuxing/Decoding|[see](examples/demuxing_decoding/main.go)|[see](https://github.com/FFmpeg/FFmpeg/blob/n7.0/doc/examples/demuxing_decoding.c)
|Filtering|[see](examples/filtering/main.go)|[see](https://github.com/FFmpeg/FFmpeg/blob/n7.0/doc/examples/filtering_video.c)
|Frame data manipulating|[see](examples/frame_data_manipulating/main.go)|X
|Frame data manipulation|[see](examples/frame_data_manipulation/main.go)|X
|Hardware Decoding|[see](examples/hardware_decoding/main.go)|[see](https://github.com/FFmpeg/FFmpeg/blob/n7.0/doc/examples/hw_decode.c)
|Remuxing|[see](examples/remuxing/main.go)|[see](https://github.com/FFmpeg/FFmpeg/blob/n7.0/doc/examples/remuxing.c)
|Scaling|[see](examples/scaling/main.go)|[see](https://github.com/FFmpeg/FFmpeg/blob/n7.0/doc/examples/scaling_video.c)
Expand Down
10 changes: 5 additions & 5 deletions astiav_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ type helperInput struct {
lastFrame *Frame
}

func (h *helper) inputFormatContext(name string) (fc *FormatContext, err error) {
func (h *helper) inputFormatContext(name string, ifmt *InputFormat) (fc *FormatContext, err error) {
h.m.Lock()
i, ok := h.inputs[name]
if ok && i.formatContext != nil {
Expand All @@ -65,7 +65,7 @@ func (h *helper) inputFormatContext(name string) (fc *FormatContext, err error)
}
h.closer.Add(fc.Free)

if err = fc.OpenInput("testdata/"+name, nil, nil); err != nil {
if err = fc.OpenInput("testdata/"+name, ifmt, nil); err != nil {
err = fmt.Errorf("astiav_test: opening input failed: %w", err)
return
}
Expand Down Expand Up @@ -95,7 +95,7 @@ func (h *helper) inputFirstPacket(name string) (pkt *Packet, err error) {
h.m.Unlock()

var fc *FormatContext
if fc, err = h.inputFormatContext(name); err != nil {
if fc, err = h.inputFormatContext(name, nil); err != nil {
err = fmt.Errorf("astiav_test: getting input format context failed")
return
}
Expand All @@ -118,7 +118,7 @@ func (h *helper) inputFirstPacket(name string) (pkt *Packet, err error) {
return
}

func (h *helper) inputLastFrame(name string, mediaType MediaType) (f *Frame, err error) {
func (h *helper) inputLastFrame(name string, mediaType MediaType, ifmt *InputFormat) (f *Frame, err error) {
h.m.Lock()
i, ok := h.inputs[name]
if ok && i.lastFrame != nil {
Expand All @@ -128,7 +128,7 @@ func (h *helper) inputLastFrame(name string, mediaType MediaType) (f *Frame, err
h.m.Unlock()

var fc *FormatContext
if fc, err = h.inputFormatContext(name); err != nil {
if fc, err = h.inputFormatContext(name, ifmt); err != nil {
err = fmt.Errorf("astiav_test: getting input format context failed: %w", err)
return
}
Expand Down
2 changes: 1 addition & 1 deletion codec_context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
)

func TestCodecContext(t *testing.T) {
fc, err := globalHelper.inputFormatContext("video.mp4")
fc, err := globalHelper.inputFormatContext("video.mp4", nil)
require.NoError(t, err)
ss := fc.Streams()
require.Len(t, ss, 2)
Expand Down
2 changes: 1 addition & 1 deletion codec_parameters_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
)

func TestCodecParameters(t *testing.T) {
fc, err := globalHelper.inputFormatContext("video.mp4")
fc, err := globalHelper.inputFormatContext("video.mp4", nil)
require.NoError(t, err)
ss := fc.Streams()
require.Len(t, ss, 2)
Expand Down
108 changes: 0 additions & 108 deletions examples/frame_data_manipulating/main.go

This file was deleted.

129 changes: 129 additions & 0 deletions examples/frame_data_manipulation/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package main

import (
"fmt"
"image"
"log"
"strings"

"github.com/asticode/go-astiav"
)

func main() {
// Handle ffmpeg logs
astiav.SetLogLevel(astiav.LogLevelDebug)
astiav.SetLogCallback(func(c astiav.Classer, l astiav.LogLevel, fmt, msg string) {
var cs string
if c != nil {
if cl := c.Class(); cl != nil {
cs = " - class: " + cl.String()
}
}
log.Printf("ffmpeg log: %s%s - level: %d\n", strings.TrimSpace(msg), cs, l)
})

/*
In this first part we're going to manipulate an audio frame
*/

// Alloc frame
audioFrame := astiav.AllocFrame()
defer audioFrame.Free()

// To write data manually into a frame, proper attributes need to be set and allocated
audioFrame.SetChannelLayout(astiav.ChannelLayoutStereo)
audioFrame.SetNbSamples(960)
audioFrame.SetSampleFormat(astiav.SampleFormatFlt)
audioFrame.SetSampleRate(48000)

// Alloc buffer
align := 0
if err := audioFrame.AllocBuffer(align); err != nil {
log.Fatal(fmt.Errorf("main: allocating buffer failed: %w", err))
}

// Alloc samples
if err := audioFrame.AllocSamples(align); err != nil {
log.Fatal(fmt.Errorf("main: allocating image failed: %w", err))
}

// When writing data manually into a frame, you need to make sure the frame is writable
if err := audioFrame.MakeWritable(); err != nil {
log.Fatal(fmt.Errorf("main: making frame writable failed: %w", err))
}

// Let's say b1 contains an actual audio buffer, we can update the audio frame's data based on the buffer
var b1 []byte
if err := audioFrame.Data().SetBytes(b1, align); err != nil {
log.Fatal(fmt.Errorf("main: setting frame's data based from bytes failed: %w", err))
}

// We can also retrieve the audio frame's data as buffer
if _, err := audioFrame.Data().Bytes(align); err != nil {
log.Fatal(fmt.Errorf("main: getting frame's data as bytes failed: %w", err))
}

/*
In this second part we're going to manipulate a video frame
*/

// Alloc frame
videoFrame := astiav.AllocFrame()
defer videoFrame.Free()

// To write data manually into a frame, proper attributes need to be set and allocated
videoFrame.SetHeight(256)
videoFrame.SetPixelFormat(astiav.PixelFormatRgba)
videoFrame.SetWidth(256)

// Alloc buffer
align = 1
if err := videoFrame.AllocBuffer(align); err != nil {
log.Fatal(fmt.Errorf("main: allocating buffer failed: %w", err))
}

// Alloc image
if err := videoFrame.AllocImage(align); err != nil {
log.Fatal(fmt.Errorf("main: allocating image failed: %w", err))
}

// When writing data manually into a frame, you need to make sure the frame is writable
if err := videoFrame.MakeWritable(); err != nil {
log.Fatal(fmt.Errorf("main: making frame writable failed: %w", err))
}

// Let's say b2 contains an actual video buffer, we can update the video frame's data based on the buffer
var b2 []byte
if err := videoFrame.Data().SetBytes(b2, align); err != nil {
log.Fatal(fmt.Errorf("main: setting frame's data based from bytes failed: %w", err))
}

// We can also retrieve the video frame's data as buffer
if _, err := videoFrame.Data().Bytes(align); err != nil {
log.Fatal(fmt.Errorf("main: getting frame's data as bytes failed: %w", err))
}

// Let's say i1 is an actual Go image.Image, we can update the video frame's data based on the image
var i1 image.Image
if err := videoFrame.Data().FromImage(i1); err != nil {
log.Fatal(fmt.Errorf("main: setting frame's data based on Go image failed: %w", err))
}

// We can also retrieve the video frame's data as a Go image
// For that we first need to guess the Go image format based on the frame's attributes before providing
// it to .ToImage(). You may not need this and can provide your own image.Image to .ToImage()
i2, err := videoFrame.Data().GuessImageFormat()
if err != nil {
log.Fatal(fmt.Errorf("main: guessing image format failed: %w", err))
}
if err := videoFrame.Data().ToImage(i2); err != nil {
log.Fatal(fmt.Errorf("main: getting frame's data as Go image failed: %w", err))
}

// Success
log.Println("success")
}
2 changes: 1 addition & 1 deletion format_context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
)

func TestFormatContext(t *testing.T) {
fc1, err := globalHelper.inputFormatContext("video.mp4")
fc1, err := globalHelper.inputFormatContext("video.mp4", nil)
require.NoError(t, err)
ss := fc1.Streams()
require.Len(t, ss, 2)
Expand Down
22 changes: 22 additions & 0 deletions frame.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#include <errno.h>
#include <libavutil/avutil.h>
#include <libavutil/samplefmt.h>
#include <stdint.h>
#include <string.h>

int astiavSamplesCopyToBuffer(uint8_t* dst, int dst_size, const uint8_t * const src_data[8], int nb_channels, int nb_samples, enum AVSampleFormat sample_fmt, int align) {
int linesize, buffer_size, nb_planes, i;

buffer_size = av_samples_get_buffer_size(&linesize, nb_channels, nb_samples, sample_fmt, align);
if (buffer_size > dst_size || buffer_size < 0) return AVERROR(EINVAL);

nb_planes = buffer_size / linesize;

for (i = 0; i < nb_planes; i++) {
const uint8_t *src = src_data[i];
memcpy(dst, src, linesize);
dst += linesize;
src += linesize;
}
return buffer_size;
}
21 changes: 21 additions & 0 deletions frame.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package astiav
//#include <libavutil/imgutils.h>
//#include <libavutil/samplefmt.h>
//#include <libavutil/hwcontext.h>
//#include "frame.h"
import "C"
import (
"unsafe"
Expand Down Expand Up @@ -113,6 +114,26 @@ func (f *Frame) ImageFillBlack() error {
return newError(C.av_image_fill_black(&f.c.data[0], &linesize[0], (C.enum_AVPixelFormat)(f.c.format), (C.enum_AVColorRange)(f.c.color_range), f.c.width, f.c.height))
}

func (f *Frame) SamplesBufferSize(align int) (int, error) {
ret := C.av_samples_get_buffer_size(nil, f.c.ch_layout.nb_channels, f.c.nb_samples, (C.enum_AVSampleFormat)(f.c.format), C.int(align))
if err := newError(ret); err != nil {
return 0, err
}
return int(ret), nil
}

func (f *Frame) SamplesCopyToBuffer(b []byte, align int) (int, error) {
ret := C.astiavSamplesCopyToBuffer((*C.uint8_t)(unsafe.Pointer(&b[0])), C.int(len(b)), &f.c.data[0], f.c.ch_layout.nb_channels, f.c.nb_samples, (C.enum_AVSampleFormat)(f.c.format), C.int(align))
if err := newError(ret); err != nil {
return 0, err
}
return int(ret), nil
}

func (f *Frame) SamplesFillSilence() error {
return newError(C.av_samples_set_silence(&f.c.data[0], 0, f.c.nb_samples, f.c.ch_layout.nb_channels, (C.enum_AVSampleFormat)(f.c.format)))
}

func (f *Frame) Linesize() [NumDataPointers]int {
o := [NumDataPointers]int{}
for i := 0; i < int(NumDataPointers); i++ {
Expand Down
4 changes: 4 additions & 0 deletions frame.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#include <libavutil/samplefmt.h>
#include <stdint.h>

int astiavSamplesCopyToBuffer(uint8_t* dst, int dst_size, const uint8_t * const src_data[8], int nb_channels, int nb_samples, enum AVSampleFormat sample_fmt, int align);
Loading

0 comments on commit c8b4cbe

Please sign in to comment.