-
Notifications
You must be signed in to change notification settings - Fork 29
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
17 changed files
with
655 additions
and
72 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
137 changes: 137 additions & 0 deletions
137
examples/client-codec-h264-convert-to-jpeg/h264_decoder.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"image" | ||
"unsafe" | ||
) | ||
|
||
// #cgo pkg-config: libavcodec libavutil libswscale | ||
// #include <libavcodec/avcodec.h> | ||
// #include <libavutil/imgutils.h> | ||
// #include <libswscale/swscale.h> | ||
import "C" | ||
|
||
func frameData(frame *C.AVFrame) **C.uint8_t { | ||
return (**C.uint8_t)(unsafe.Pointer(&frame.data[0])) | ||
} | ||
|
||
func frameLineSize(frame *C.AVFrame) *C.int { | ||
return (*C.int)(unsafe.Pointer(&frame.linesize[0])) | ||
} | ||
|
||
// h264Decoder is a wrapper around FFmpeg's H264 decoder. | ||
type h264Decoder struct { | ||
codecCtx *C.AVCodecContext | ||
srcFrame *C.AVFrame | ||
swsCtx *C.struct_SwsContext | ||
dstFrame *C.AVFrame | ||
dstFramePtr []uint8 | ||
} | ||
|
||
// initialize initializes a h264Decoder. | ||
func (d *h264Decoder) initialize() error { | ||
codec := C.avcodec_find_decoder(C.AV_CODEC_ID_H264) | ||
if codec == nil { | ||
return fmt.Errorf("avcodec_find_decoder() failed") | ||
} | ||
|
||
d.codecCtx = C.avcodec_alloc_context3(codec) | ||
if d.codecCtx == nil { | ||
return fmt.Errorf("avcodec_alloc_context3() failed") | ||
} | ||
|
||
res := C.avcodec_open2(d.codecCtx, codec, nil) | ||
if res < 0 { | ||
C.avcodec_close(d.codecCtx) | ||
return fmt.Errorf("avcodec_open2() failed") | ||
} | ||
|
||
d.srcFrame = C.av_frame_alloc() | ||
if d.srcFrame == nil { | ||
C.avcodec_close(d.codecCtx) | ||
return fmt.Errorf("av_frame_alloc() failed") | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// close closes the decoder. | ||
func (d *h264Decoder) close() { | ||
if d.dstFrame != nil { | ||
C.av_frame_free(&d.dstFrame) | ||
} | ||
|
||
if d.swsCtx != nil { | ||
C.sws_freeContext(d.swsCtx) | ||
} | ||
|
||
C.av_frame_free(&d.srcFrame) | ||
C.avcodec_close(d.codecCtx) | ||
} | ||
|
||
func (d *h264Decoder) decode(nalu []byte) (image.Image, error) { | ||
nalu = append([]uint8{0x00, 0x00, 0x00, 0x01}, []uint8(nalu)...) | ||
|
||
// send NALU to decoder | ||
var avPacket C.AVPacket | ||
avPacket.data = (*C.uint8_t)(C.CBytes(nalu)) | ||
defer C.free(unsafe.Pointer(avPacket.data)) | ||
avPacket.size = C.int(len(nalu)) | ||
res := C.avcodec_send_packet(d.codecCtx, &avPacket) | ||
if res < 0 { | ||
return nil, nil | ||
} | ||
|
||
// receive frame if available | ||
res = C.avcodec_receive_frame(d.codecCtx, d.srcFrame) | ||
if res < 0 { | ||
return nil, nil | ||
} | ||
|
||
// if frame size has changed, allocate needed objects | ||
if d.dstFrame == nil || d.dstFrame.width != d.srcFrame.width || d.dstFrame.height != d.srcFrame.height { | ||
if d.dstFrame != nil { | ||
C.av_frame_free(&d.dstFrame) | ||
} | ||
|
||
if d.swsCtx != nil { | ||
C.sws_freeContext(d.swsCtx) | ||
} | ||
|
||
d.dstFrame = C.av_frame_alloc() | ||
d.dstFrame.format = C.AV_PIX_FMT_RGBA | ||
d.dstFrame.width = d.srcFrame.width | ||
d.dstFrame.height = d.srcFrame.height | ||
d.dstFrame.color_range = C.AVCOL_RANGE_JPEG | ||
res = C.av_frame_get_buffer(d.dstFrame, 1) | ||
if res < 0 { | ||
return nil, fmt.Errorf("av_frame_get_buffer() failed") | ||
} | ||
|
||
d.swsCtx = C.sws_getContext(d.srcFrame.width, d.srcFrame.height, C.AV_PIX_FMT_YUV420P, | ||
d.dstFrame.width, d.dstFrame.height, (int32)(d.dstFrame.format), C.SWS_BILINEAR, nil, nil, nil) | ||
if d.swsCtx == nil { | ||
return nil, fmt.Errorf("sws_getContext() failed") | ||
} | ||
|
||
dstFrameSize := C.av_image_get_buffer_size((int32)(d.dstFrame.format), d.dstFrame.width, d.dstFrame.height, 1) | ||
d.dstFramePtr = (*[1 << 30]uint8)(unsafe.Pointer(d.dstFrame.data[0]))[:dstFrameSize:dstFrameSize] | ||
} | ||
|
||
// convert color space from YUV420 to RGBA | ||
res = C.sws_scale(d.swsCtx, frameData(d.srcFrame), frameLineSize(d.srcFrame), | ||
0, d.srcFrame.height, frameData(d.dstFrame), frameLineSize(d.dstFrame)) | ||
if res < 0 { | ||
return nil, fmt.Errorf("sws_scale() failed") | ||
} | ||
|
||
// embed frame into an image.Image | ||
return &image.RGBA{ | ||
Pix: d.dstFramePtr, | ||
Stride: 4 * (int)(d.dstFrame.width), | ||
Rect: image.Rectangle{ | ||
Max: image.Point{(int)(d.dstFrame.width), (int)(d.dstFrame.height)}, | ||
}, | ||
}, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"image" | ||
"image/jpeg" | ||
"log" | ||
"os" | ||
"strconv" | ||
"time" | ||
|
||
"github.com/bluenviron/gohlslib" | ||
"github.com/bluenviron/gohlslib/pkg/codecs" | ||
) | ||
|
||
// This example shows how to | ||
// 1. read a HLS stream | ||
// 2. check if there's a H264 track | ||
// 3. decode H264 access units into RGBA frames | ||
// 4. convert frames to JPEG images and save them on disk | ||
|
||
// This example requires the FFmpeg libraries, that can be installed with this command: | ||
// apt install -y libavformat-dev libswscale-dev gcc pkg-config | ||
|
||
func findH264Track(tracks []*gohlslib.Track) *gohlslib.Track { | ||
for _, track := range tracks { | ||
if _, ok := track.Codec.(*codecs.H264); ok { | ||
return track | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
func saveToFile(img image.Image) error { | ||
// create file | ||
fname := strconv.FormatInt(time.Now().UnixNano()/int64(time.Millisecond), 10) + ".jpg" | ||
f, err := os.Create(fname) | ||
if err != nil { | ||
panic(err) | ||
} | ||
defer f.Close() | ||
|
||
log.Println("saving", fname) | ||
|
||
// convert to jpeg | ||
return jpeg.Encode(f, img, &jpeg.Options{ | ||
Quality: 60, | ||
}) | ||
} | ||
|
||
func main() { | ||
// setup client | ||
c := &gohlslib.Client{ | ||
URI: "https://myserver/mystream/index.m3u8", | ||
} | ||
|
||
// called when tracks are parsed | ||
c.OnTracks = func(tracks []*gohlslib.Track) error { | ||
// find the H264 track | ||
track := findH264Track(tracks) | ||
if track == nil { | ||
return fmt.Errorf("H264 track not found") | ||
} | ||
|
||
// create the H264 decoder | ||
frameDec := &h264Decoder{} | ||
err := frameDec.initialize() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// if SPS and PPS are present into the track, send them to the decoder | ||
if track.Codec.(*codecs.H264).SPS != nil { | ||
frameDec.decode(track.Codec.(*codecs.H264).SPS) | ||
} | ||
if track.Codec.(*codecs.H264).PPS != nil { | ||
frameDec.decode(track.Codec.(*codecs.H264).PPS) | ||
} | ||
|
||
saveCount := 0 | ||
|
||
// set a callback that is called when data is received | ||
c.OnDataH26x(track, func(pts time.Duration, dts time.Duration, au [][]byte) { | ||
log.Printf("received access unit with pts = %v\n", pts) | ||
|
||
for _, nalu := range au { | ||
// convert NALUs into RGBA frames | ||
img, err := frameDec.decode(nalu) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
// wait for a frame | ||
if img == nil { | ||
continue | ||
} | ||
|
||
// convert frame to JPEG and save to file | ||
err = saveToFile(img) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
saveCount++ | ||
if saveCount == 5 { | ||
log.Printf("saved 5 images, exiting") | ||
os.Exit(1) | ||
} | ||
} | ||
}) | ||
|
||
return nil | ||
} | ||
|
||
// start reading | ||
err := c.Start() | ||
if err != nil { | ||
panic(err) | ||
} | ||
defer c.Close() | ||
|
||
// wait for a fatal error | ||
panic(<-c.Wait()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"log" | ||
"time" | ||
|
||
"github.com/bluenviron/gohlslib" | ||
"github.com/bluenviron/gohlslib/pkg/codecs" | ||
) | ||
|
||
// This example shows how to | ||
// 1. read a HLS stream | ||
// 2. check if there's a H264 track | ||
// 3. save the H264 track to disk in MPEG-TS format | ||
|
||
func findH264Track(tracks []*gohlslib.Track) *gohlslib.Track { | ||
for _, track := range tracks { | ||
if _, ok := track.Codec.(*codecs.H264); ok { | ||
return track | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
func main() { | ||
// setup client | ||
c := &gohlslib.Client{ | ||
URI: "https://myserver/mystream/index.m3u8", | ||
} | ||
|
||
// called when tracks are parsed | ||
c.OnTracks = func(tracks []*gohlslib.Track) error { | ||
// find the H264 track | ||
track := findH264Track(tracks) | ||
if track == nil { | ||
return fmt.Errorf("H264 track not found") | ||
} | ||
|
||
// create the MPEG-TS muxer | ||
m := &mpegtsMuxer{ | ||
fileName: "mystream.ts", | ||
sps: track.Codec.(*codecs.H264).SPS, | ||
pps: track.Codec.(*codecs.H264).PPS, | ||
} | ||
err := m.initialize() | ||
if err != nil { | ||
return nil | ||
} | ||
|
||
// set a callback that is called when data is received | ||
c.OnDataH26x(track, func(pts time.Duration, dts time.Duration, au [][]byte) { | ||
log.Printf("received access unit with pts = %v\n", pts) | ||
|
||
// send data to the MPEG-TS muxer | ||
err := m.writeH264(au, pts) | ||
if err != nil { | ||
panic(err) | ||
} | ||
}) | ||
|
||
return nil | ||
} | ||
|
||
// start reading | ||
err := c.Start() | ||
if err != nil { | ||
panic(err) | ||
} | ||
defer c.Close() | ||
|
||
// wait for a fatal error | ||
panic(<-c.Wait()) | ||
} |
Oops, something went wrong.