mirror of
https://github.com/qrtc/ffmpeg-dev-go.git
synced 2025-10-05 15:47:33 +08:00
2023-10-31 09:16:44 CST W44D2
This commit is contained in:
351
examples/demux-decode/main.go
Normal file
351
examples/demux-decode/main.go
Normal file
@@ -0,0 +1,351 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
ffmpeg "github.com/qrtc/ffmpeg-dev-go"
|
||||
)
|
||||
|
||||
var (
|
||||
audioStream *ffmpeg.AVStream
|
||||
videoStream *ffmpeg.AVStream
|
||||
videoFrameCount int
|
||||
audioFrameCount int
|
||||
width, height int32
|
||||
pixFmt ffmpeg.AVPixelFormat
|
||||
audioDstFile *os.File
|
||||
videoDstFile *os.File
|
||||
audioStreamIdx int32
|
||||
videoStreamIdx int32
|
||||
videoDstData [4]*uint8
|
||||
videoDstLinesize [4]int32
|
||||
videoDstBufSize int32
|
||||
srcFilename string
|
||||
)
|
||||
|
||||
func outputVideoFrame(videoDecCtx *ffmpeg.AVCodecContext, frame *ffmpeg.AVFrame) int32 {
|
||||
|
||||
if frame.GetWidth() != width || frame.GetHeight() != height || frame.GetFormat() != pixFmt {
|
||||
// To handle this change, one could call av_image_alloc again and
|
||||
// decode the following frames into another rawvideo file.
|
||||
fmt.Fprintf(os.Stderr, "Error: Width, height and pixel format have to be "+
|
||||
"constant in a rawvideo file, but the width, height or "+
|
||||
"pixel format of the input video changed:\n"+
|
||||
"old: width = %d, height = %d, format = %s\n"+
|
||||
"new: width = %d, height = %d, format = %s\n",
|
||||
width, height, ffmpeg.AvGetPixFmtName(pixFmt),
|
||||
frame.GetWidth(), frame.GetHeight(),
|
||||
ffmpeg.AvGetPixFmtName(frame.GetFormat()))
|
||||
return -1
|
||||
}
|
||||
|
||||
videoFrameCount++
|
||||
fmt.Fprintf(os.Stdout, "video_frame n:%d coded_n:%d\n", videoFrameCount, frame.GetCodedPictureNumber())
|
||||
|
||||
// copy decoded frame to destination buffer:
|
||||
// this is required since rawvideo expects non aligned data
|
||||
ffmpeg.AvImageCopy(videoDstData[:], videoDstLinesize[:], frame.GetData(), frame.GetLinesize(), pixFmt, width, height)
|
||||
|
||||
// write to rawvideo file
|
||||
videoDstFile.Write(unsafe.Slice(videoDstData[0], videoDstBufSize))
|
||||
return 0
|
||||
}
|
||||
|
||||
func outputAudioFrame(audioDecCtx *ffmpeg.AVCodecContext, frame *ffmpeg.AVFrame) (ret int32) {
|
||||
unpaddedLinesize := frame.GetNbSamples() * ffmpeg.AvGetBytesPerSample(frame.GetFormat())
|
||||
audioFrameCount++
|
||||
fmt.Fprintf(os.Stdout, "audio_frame n:%d nb_samples:%d pts:%s\n",
|
||||
audioFrameCount, frame.GetNbSamples(),
|
||||
ffmpeg.AvTs2timestr(frame.GetPts(), audioDecCtx.GetPktTimebaseAddr()))
|
||||
|
||||
// Write the raw audio data samples of the first plane. This works
|
||||
// fine for packed formats (e.g. AV_SAMPLE_FMT_S16). However,
|
||||
// most audio decoders output planar audio, which uses a separate
|
||||
// plane of audio samples for each channel (e.g. AV_SAMPLE_FMT_S16P).
|
||||
// In other words, this code will write only the first audio channel
|
||||
// in these cases.
|
||||
// You should use libswresample or libavfilter to convert the frame
|
||||
// to packed data.
|
||||
audioDstFile.Write(ffmpeg.SliceSlice(frame.GetExtendedData(), 1, unpaddedLinesize)[0])
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func deocdePacket(dec *ffmpeg.AVCodecContext, pkt *ffmpeg.AVPacket, frame *ffmpeg.AVFrame) (ret int32) {
|
||||
|
||||
// submit the packet to the decoder
|
||||
if ret = ffmpeg.AvCodecSendPacket(dec, pkt); ret < 0 {
|
||||
fmt.Fprintf(os.Stderr, "Error submitting a packet for decoding (%s)\n", ffmpeg.AvErr2str(ret))
|
||||
return ret
|
||||
}
|
||||
|
||||
// get all the available frames from the decoder
|
||||
for ret >= 0 {
|
||||
if ret = ffmpeg.AvCodecReceiveFrame(dec, frame); ret < 0 {
|
||||
// those two return values are special and mean there is no output
|
||||
// frame available, but there were no errors during decoding
|
||||
if ret == ffmpeg.AVERROR_EOF || ret == ffmpeg.AVERROR(syscall.EAGAIN) {
|
||||
return 0
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Error during decoding (%s)\n", ffmpeg.AvErr2str(ret))
|
||||
return ret
|
||||
}
|
||||
|
||||
// write the frame data to output file
|
||||
if dec.GetCodec().GetType() == ffmpeg.AVMEDIA_TYPE_VIDEO {
|
||||
ret = outputVideoFrame(dec, frame)
|
||||
} else {
|
||||
ret = outputAudioFrame(dec, frame)
|
||||
}
|
||||
|
||||
ffmpeg.AvFrameUnref(frame)
|
||||
if ret < 0 {
|
||||
return ret
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func openCodecContext(fmtCtx *ffmpeg.AVFormatContext, _type ffmpeg.AVMediaType) (
|
||||
streamIdx int32, decCtx *ffmpeg.AVCodecContext, ret int32) {
|
||||
var (
|
||||
opts *ffmpeg.AVDictionary
|
||||
)
|
||||
if ret = ffmpeg.AvFindBestStream(fmtCtx, _type, -1, -1, nil, 0); ret < 0 {
|
||||
fmt.Fprintf(os.Stderr, "Could not find %s stream in input file '%s'\n",
|
||||
ffmpeg.AvGetMediaTypeString(_type), srcFilename)
|
||||
return 0, nil, ret
|
||||
} else {
|
||||
streamIdx = ret
|
||||
st := fmtCtx.GetStreams()[streamIdx]
|
||||
|
||||
// find decoder for the stream
|
||||
dec := ffmpeg.AvCodecFindDecoder(st.GetCodecpar().GetCodecId())
|
||||
if dec == nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to find %s codec\n",
|
||||
ffmpeg.AvGetMediaTypeString(_type))
|
||||
return 0, nil, ffmpeg.AVERROR(syscall.EINVAL)
|
||||
}
|
||||
|
||||
// Allocate a codec context for the decoder
|
||||
if decCtx = ffmpeg.AvCodecAllocContext3(dec); decCtx == nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to allocate the %s codec context\n",
|
||||
ffmpeg.AvGetMediaTypeString(_type))
|
||||
return 0, nil, ffmpeg.AVERROR(syscall.ENOMEM)
|
||||
}
|
||||
|
||||
// Copy codec parameters from input stream to output codec context
|
||||
if ret = ffmpeg.AvCodecParametersToContext(decCtx, st.GetCodecpar()); ret < 0 {
|
||||
fmt.Fprintf(os.Stderr, "Failed to copy %s codec parameters to decoder context\n",
|
||||
ffmpeg.AvGetMediaTypeString(_type))
|
||||
return 0, nil, ret
|
||||
}
|
||||
|
||||
// Init the decoders
|
||||
if ret = ffmpeg.AvCodecOpen2(decCtx, dec, &opts); ret < 0 {
|
||||
fmt.Fprintf(os.Stderr, "Failed to open %s codec\n",
|
||||
ffmpeg.AvGetMediaTypeString(_type))
|
||||
return 0, nil, ret
|
||||
}
|
||||
}
|
||||
|
||||
return streamIdx, decCtx, 0
|
||||
}
|
||||
|
||||
func getFormatFromSampleFmt(sampleFmt ffmpeg.AVSampleFormat) (string, int32) {
|
||||
sampleFmtEntry := []struct {
|
||||
sampleFmt ffmpeg.AVSampleFormat
|
||||
fmtBe string
|
||||
fmtLe string
|
||||
}{
|
||||
{ffmpeg.AV_SAMPLE_FMT_U8, "u8", "u8"},
|
||||
{ffmpeg.AV_SAMPLE_FMT_S16, "s16be", "s16le"},
|
||||
{ffmpeg.AV_SAMPLE_FMT_S32, "s32be", "s32le"},
|
||||
{ffmpeg.AV_SAMPLE_FMT_FLT, "f32be", "f32le"},
|
||||
{ffmpeg.AV_SAMPLE_FMT_DBL, "f64be", "f64le"},
|
||||
}
|
||||
|
||||
for _, entry := range sampleFmtEntry {
|
||||
if sampleFmt == entry.sampleFmt {
|
||||
return ffmpeg.AV_NE(entry.fmtBe, entry.fmtLe), 0
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stderr, "sample format %s is not supported as output format\n",
|
||||
ffmpeg.AvGetSampleFmtName(sampleFmt))
|
||||
return ffmpeg.NIL, -1
|
||||
}
|
||||
|
||||
func main() {
|
||||
var (
|
||||
ret int32
|
||||
err error
|
||||
fmtCtx *ffmpeg.AVFormatContext
|
||||
audioDecCtx *ffmpeg.AVCodecContext
|
||||
videoDecCtx *ffmpeg.AVCodecContext
|
||||
frame *ffmpeg.AVFrame
|
||||
pkt *ffmpeg.AVPacket
|
||||
)
|
||||
if len(os.Args) != 4 {
|
||||
fmt.Fprintf(os.Stderr, "usage: %s input_file video_output_file audio_output_file\n"+
|
||||
"API example program to show how to read frames from an input file.\n"+
|
||||
"This program reads frames from a file, decodes them, and writes decoded\n"+
|
||||
"video frames to a rawvideo file named video_output_file, and decoded\n"+
|
||||
"audio frames to a rawaudio file named audio_output_file.\n", os.Args[0])
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
srcFilename = os.Args[1]
|
||||
videoDstFilename := os.Args[2]
|
||||
audioDstFilename := os.Args[3]
|
||||
|
||||
// open input file, and allocate format context
|
||||
if ret = ffmpeg.AvFormatOpenInput(&fmtCtx, srcFilename, nil, nil); ret < 0 {
|
||||
fmt.Fprintf(os.Stderr, "Could not open source file %s\n", srcFilename)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// retrieve stream information
|
||||
if videoStreamIdx, videoDecCtx, ret = openCodecContext(fmtCtx, ffmpeg.AVMEDIA_TYPE_VIDEO); ret >= 0 {
|
||||
videoStream = fmtCtx.GetStreams()[videoStreamIdx]
|
||||
|
||||
videoDstFile, err = os.OpenFile(videoDstFilename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Could not open destination file %s\n", videoDstFilename)
|
||||
ret = 1
|
||||
goto end
|
||||
}
|
||||
|
||||
// allocate image where the decoded image will be put
|
||||
width = videoDecCtx.GetWidth()
|
||||
height = videoDecCtx.GetHeight()
|
||||
pixFmt = videoDecCtx.GetPixFmt()
|
||||
if pixFmt == ffmpeg.AV_PIX_FMT_NONE {
|
||||
pixFmt = ffmpeg.AV_PIX_FMT_YUV420P
|
||||
}
|
||||
if ret = ffmpeg.AvImageAlloc(videoDstData[:], videoDstLinesize[:],
|
||||
width, height, pixFmt, 1); ret < 0 {
|
||||
fmt.Fprintf(os.Stderr, "Could not allocate raw video buffer (%s)\n", ffmpeg.AvErr2str(ret))
|
||||
goto end
|
||||
}
|
||||
videoDstBufSize = ret
|
||||
}
|
||||
|
||||
if audioStreamIdx, audioDecCtx, ret = openCodecContext(fmtCtx, ffmpeg.AVMEDIA_TYPE_AUDIO); ret >= 0 {
|
||||
audioStream = fmtCtx.GetStreams()[audioStreamIdx]
|
||||
|
||||
audioDstFile, err = os.OpenFile(audioDstFilename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Could not open destination file %s\n", audioDstFilename)
|
||||
ret = 1
|
||||
goto end
|
||||
}
|
||||
}
|
||||
|
||||
// dump input information to stderr
|
||||
ffmpeg.AvDumpFormat(fmtCtx, 0, srcFilename, 0)
|
||||
|
||||
if audioStream == nil && videoStream == nil {
|
||||
fmt.Fprintf(os.Stderr, "Could not find audio or video stream in the input, aborting\n")
|
||||
ret = 1
|
||||
goto end
|
||||
}
|
||||
|
||||
if frame = ffmpeg.AvFrameAlloc(); frame == nil {
|
||||
fmt.Fprintf(os.Stderr, "Could not allocate frame\n")
|
||||
ret = ffmpeg.AVERROR(syscall.ENOMEM)
|
||||
goto end
|
||||
}
|
||||
|
||||
if pkt = ffmpeg.AvPacketAlloc(); pkt == nil {
|
||||
fmt.Fprintf(os.Stderr, "Could not allocate packet\n")
|
||||
ret = ffmpeg.AVERROR(syscall.ENOMEM)
|
||||
goto end
|
||||
}
|
||||
|
||||
if videoStream != nil {
|
||||
fmt.Fprintf(os.Stdout, "Demuxing video from file '%s' into '%s'\n", srcFilename, videoDstFilename)
|
||||
}
|
||||
if audioStream != nil {
|
||||
fmt.Fprintf(os.Stdout, "Demuxing audio from file '%s' into '%s'\n", srcFilename, audioDstFilename)
|
||||
}
|
||||
|
||||
// read frames from the file
|
||||
for ffmpeg.AvReadFrame(fmtCtx, pkt) >= 0 {
|
||||
// check if the packet belongs to a stream we are interested in, otherwise
|
||||
// skip it
|
||||
if pkt.GetStreamIndex() == videoStreamIdx {
|
||||
ret = deocdePacket(videoDecCtx, pkt, frame)
|
||||
} else if pkt.GetStreamIndex() == audioStreamIdx {
|
||||
ret = deocdePacket(audioDecCtx, pkt, frame)
|
||||
}
|
||||
ffmpeg.AvPacketUnref(pkt)
|
||||
if ret < 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// flush the decoders
|
||||
if videoDecCtx != nil {
|
||||
deocdePacket(videoDecCtx, nil, frame)
|
||||
}
|
||||
if audioDecCtx != nil {
|
||||
deocdePacket(audioDecCtx, nil, frame)
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stdout, "Demuxing succeeded.\n")
|
||||
|
||||
if videoStream != nil {
|
||||
fmt.Fprintf(os.Stdout, "Play the output video file with the command:\n"+
|
||||
"ffplay -f rawvideo -pixel_format %s -video_size %dx%d %s\n",
|
||||
ffmpeg.AvGetPixFmtName(videoDecCtx.GetPixFmt()),
|
||||
videoDecCtx.GetWidth(), videoDecCtx.GetHeight(), videoDstFilename)
|
||||
}
|
||||
|
||||
if audioStream != nil {
|
||||
sfmt := audioDecCtx.GetSampleFmt()
|
||||
nChannels := audioDecCtx.GetChannels()
|
||||
var _fmt string
|
||||
|
||||
if ffmpeg.AvSampleFmtIsPlanar(sfmt) != 0 {
|
||||
packed := ffmpeg.AvGetSampleFmtName(sfmt)
|
||||
if len(packed) == 0 {
|
||||
packed = "?"
|
||||
}
|
||||
fmt.Fprintf(os.Stdout, "Warning: the sample format the decoder produced is planar "+
|
||||
"(%s). This example will output the first channel only.\n", packed)
|
||||
sfmt = ffmpeg.AvGetPackedSampleFmt(sfmt)
|
||||
nChannels = 1
|
||||
}
|
||||
|
||||
if _fmt, ret = getFormatFromSampleFmt(sfmt); ret < 0 {
|
||||
goto end
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stdout, "Play the output audio file with the command:\n"+
|
||||
"ffplay -f %s -ac %d -ar %d %s\n",
|
||||
_fmt, nChannels, audioDecCtx.GetSampleRate(), audioDstFilename)
|
||||
}
|
||||
|
||||
end:
|
||||
ffmpeg.AvCodecFreeContext(&videoDecCtx)
|
||||
ffmpeg.AvCodecFreeContext(&audioDecCtx)
|
||||
ffmpeg.AvFormatCloseInput(&fmtCtx)
|
||||
if videoDstFile != nil {
|
||||
videoDstFile.Close()
|
||||
}
|
||||
if audioDstFile != nil {
|
||||
audioDstFile.Close()
|
||||
}
|
||||
ffmpeg.AvPacketFree(&pkt)
|
||||
ffmpeg.AvFrameFree(&frame)
|
||||
ffmpeg.AvFree(videoDstData[0])
|
||||
if ret < 0 {
|
||||
os.Exit(int(ret))
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user