mirror of
https://github.com/Danile71/go-rtsp.git
synced 2025-11-02 12:24:17 +08:00
first release
This commit is contained in:
10
example/go.mod
Normal file
10
example/go.mod
Normal file
@@ -0,0 +1,10 @@
|
||||
module example
|
||||
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
github.com/Danile71/go-logger v0.0.6
|
||||
github.com/Danile71/go-rtsp v0.0.1
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/mattn/go-mjpeg v0.0.1
|
||||
)
|
||||
7
example/go.sum
Normal file
7
example/go.sum
Normal file
@@ -0,0 +1,7 @@
|
||||
github.com/Danile71/go-logger v0.1.1 h1:8Ge3aIqbF9m225bgEibTUp8GHFEBZs0zi41s4/hYXqA=
|
||||
github.com/Danile71/go-logger v0.1.1/go.mod h1:nCZhBJNLRHmvdCl+W6w0uWnUbm/gpezqV1B34+Ecy1c=
|
||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/mattn/go-mjpeg v0.0.1 h1:PunfTzQvXkULfur7ALrU8EeMWM4kknyJT7zI1DUoNIQ=
|
||||
github.com/mattn/go-mjpeg v0.0.1/go.mod h1:m4++VtXCgYJxuZeEUF3WydpJk+0yKO50el52Ww5xBvo=
|
||||
gocv.io/x/gocv v0.24.0/go.mod h1:Rar2PS6DV+T4FL+PM535EImD/h13hGVaHhnCu1xarBs=
|
||||
43
example/main.go
Normal file
43
example/main.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/Danile71/go-logger"
|
||||
"github.com/Danile71/go-rtsp"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/mattn/go-mjpeg"
|
||||
)
|
||||
|
||||
const uri = "rtsp://admin:admin@127.0.0.1:554"
|
||||
|
||||
func main() {
|
||||
s := mjpeg.NewStream()
|
||||
|
||||
stream, err := rtsp.Open(uri)
|
||||
if logger.OnError(err) {
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
pkt, err := stream.ReadPacket()
|
||||
if logger.OnError(err) {
|
||||
continue
|
||||
}
|
||||
|
||||
if pkt.IsVideo() {
|
||||
s.Update(pkt.Data())
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
streamHandler := func(w http.ResponseWriter, r *http.Request) {
|
||||
s.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
router := mux.NewRouter()
|
||||
router.HandleFunc("/stream", streamHandler)
|
||||
http.Handle("/", router)
|
||||
http.ListenAndServe(":8181", nil)
|
||||
}
|
||||
158
ffmpeg.c
Normal file
158
ffmpeg.c
Normal file
@@ -0,0 +1,158 @@
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavutil/avutil.h>
|
||||
#include <libavutil/imgutils.h>
|
||||
#include <libswresample/swresample.h>
|
||||
#include <libswscale/swscale.h>
|
||||
#include <libavutil/opt.h>
|
||||
#include <string.h>
|
||||
#include "ffmpeg.h"
|
||||
|
||||
|
||||
struct AVStream * stream_at(struct AVFormatContext *c, int idx) {
|
||||
if (idx >= 0 && idx < c->nb_streams)
|
||||
return c->streams[idx];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int open(AVFormatContext* format_ctx,AVCodecContext* codec_ctx,const char *uri)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = avformat_open_input(&format_ctx, uri, NULL, NULL);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return avformat_find_stream_info(format_ctx, NULL);
|
||||
}
|
||||
|
||||
int decode2(AVCodecContext *avctx, AVFrame *frame,AVPacket *pkt, int *got_frame)
|
||||
{
|
||||
int ret;
|
||||
|
||||
*got_frame = 0;
|
||||
|
||||
ret = avcodec_send_packet(avctx, pkt);
|
||||
|
||||
av_packet_unref(pkt);
|
||||
|
||||
if (ret < 0)
|
||||
return ret == AVERROR_EOF ? 0 : ret;
|
||||
|
||||
|
||||
ret = avcodec_receive_frame(avctx, frame);
|
||||
if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF)
|
||||
return ret;
|
||||
if (ret >= 0)
|
||||
*got_frame = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int decode(AVCodecContext *avctx, AVFrame *frame, uint8_t *data, int size, int *got_frame)
|
||||
{
|
||||
int ret;
|
||||
struct AVPacket pkt = {.data = data, .size = size};
|
||||
|
||||
*got_frame = 0;
|
||||
|
||||
ret = avcodec_send_packet(avctx, &pkt);
|
||||
|
||||
av_packet_unref(&pkt);
|
||||
|
||||
if (ret < 0)
|
||||
return ret == AVERROR_EOF ? 0 : ret;
|
||||
|
||||
|
||||
ret = avcodec_receive_frame(avctx, frame);
|
||||
if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF)
|
||||
return ret;
|
||||
if (ret >= 0)
|
||||
*got_frame = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int encode(AVCodecContext *avctx, AVPacket *pkt, int *got_packet, AVFrame *frame)
|
||||
{
|
||||
int ret;
|
||||
|
||||
*got_packet = 0;
|
||||
|
||||
ret = avcodec_send_frame(avctx, frame);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = avcodec_receive_packet(avctx, pkt);
|
||||
if (!ret)
|
||||
*got_packet = 1;
|
||||
if (ret == AVERROR(EAGAIN))
|
||||
return 0;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int avcodec_encode_jpeg(AVCodecContext *pCodecCtx, AVFrame *pFrame,AVPacket *packet) {
|
||||
AVCodec *jpegCodec = avcodec_find_encoder(AV_CODEC_ID_MJPEG);
|
||||
int ret = -1;
|
||||
|
||||
if (!jpegCodec) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
AVCodecContext *jpegContext = avcodec_alloc_context3(jpegCodec);
|
||||
if (!jpegContext) {
|
||||
jpegCodec = NULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
jpegContext->pix_fmt = AV_PIX_FMT_YUVJ420P;
|
||||
jpegContext->height = pFrame->height;
|
||||
jpegContext->width = pFrame->width;
|
||||
jpegContext->time_base= (AVRational){1,25};
|
||||
|
||||
ret = avcodec_open2(jpegContext, jpegCodec, NULL);
|
||||
if (ret < 0) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
int gotFrame;
|
||||
|
||||
ret = encode(jpegContext, packet, &gotFrame, pFrame);
|
||||
if (ret < 0) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
error:
|
||||
avcodec_close(jpegContext);
|
||||
avcodec_free_context(&jpegContext);
|
||||
jpegCodec = NULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint8_t *convert(AVCodecContext *pCodecCtx,AVFrame *pFrame,AVFrame *nFrame,int *size, int format) {
|
||||
struct SwsContext *img_convert_ctx = sws_getCachedContext( NULL, pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pFrame->width, pFrame->height, format, SWS_BICUBIC, NULL, NULL, NULL );
|
||||
nFrame->format = format;
|
||||
nFrame->width = pFrame->width;
|
||||
nFrame->height = pFrame->height;
|
||||
|
||||
*size = av_image_get_buffer_size( format, pFrame->width, pFrame->height, 1);
|
||||
|
||||
uint8_t *tmp_picture_buf = (uint8_t *)malloc(*size);
|
||||
|
||||
av_image_fill_arrays(nFrame->data, nFrame->linesize, tmp_picture_buf, format, pFrame->width, pFrame->height, 1);
|
||||
|
||||
sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, nFrame->height, nFrame->data, nFrame->linesize);
|
||||
sws_freeContext(img_convert_ctx);
|
||||
return tmp_picture_buf;
|
||||
}
|
||||
|
||||
int avcodec_encode_jpeg_nv12(AVCodecContext *pCodecCtx, AVFrame *pFrame,AVFrame *nFrame,AVPacket *packet) {
|
||||
int size = 0;
|
||||
uint8_t * data = convert(pCodecCtx, pFrame, nFrame, &size, AV_PIX_FMT_YUV420P);
|
||||
int ret = avcodec_encode_jpeg(pCodecCtx,nFrame,packet);
|
||||
free(data);
|
||||
return ret;
|
||||
}
|
||||
|
||||
10
ffmpeg.h
Normal file
10
ffmpeg.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavutil/avutil.h>
|
||||
|
||||
uint8_t *convert(AVCodecContext *pCodecCtx,AVFrame *pFrame,AVFrame *nFrame,int *size, int format);
|
||||
int avcodec_encode_jpeg(AVCodecContext *pCodecCtx, AVFrame *pFrame,AVPacket *packet);
|
||||
int avcodec_encode_jpeg_nv12(AVCodecContext *pCodecCtx, AVFrame *pFrame,AVFrame *nFrame,AVPacket *packet);
|
||||
|
||||
int open(AVFormatContext* format_ctx,AVCodecContext* codec_ctx,const char *uri);
|
||||
struct AVStream * stream_at(struct AVFormatContext *c, int idx) ;
|
||||
7
go.sum
Normal file
7
go.sum
Normal file
@@ -0,0 +1,7 @@
|
||||
github.com/Danile71/go-logger v0.1.1 h1:8Ge3aIqbF9m225bgEibTUp8GHFEBZs0zi41s4/hYXqA=
|
||||
github.com/Danile71/go-logger v0.1.1/go.mod h1:nCZhBJNLRHmvdCl+W6w0uWnUbm/gpezqV1B34+Ecy1c=
|
||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/mattn/go-mjpeg v0.0.1 h1:PunfTzQvXkULfur7ALrU8EeMWM4kknyJT7zI1DUoNIQ=
|
||||
github.com/mattn/go-mjpeg v0.0.1/go.mod h1:m4++VtXCgYJxuZeEUF3WydpJk+0yKO50el52Ww5xBvo=
|
||||
gocv.io/x/gocv v0.24.0/go.mod h1:Rar2PS6DV+T4FL+PM535EImD/h13hGVaHhnCu1xarBs=
|
||||
32
pkt.go
Normal file
32
pkt.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package rtsp
|
||||
|
||||
// #include "ffmpeg.h"
|
||||
import "C"
|
||||
|
||||
type Packet struct {
|
||||
streamIndex int
|
||||
codecType int
|
||||
data []byte
|
||||
width int
|
||||
height int
|
||||
}
|
||||
|
||||
func (pkt *Packet) Height() int {
|
||||
return pkt.height
|
||||
}
|
||||
|
||||
func (pkt *Packet) Width() int {
|
||||
return pkt.width
|
||||
}
|
||||
|
||||
func (pkt *Packet) Data() []byte {
|
||||
return pkt.data
|
||||
}
|
||||
|
||||
func (pkt *Packet) IsAudio() bool {
|
||||
return pkt.codecType == C.AVMEDIA_TYPE_AUDIO
|
||||
}
|
||||
|
||||
func (pkt *Packet) IsVideo() bool {
|
||||
return pkt.codecType == C.AVMEDIA_TYPE_VIDEO
|
||||
}
|
||||
192
rtsp.go
Normal file
192
rtsp.go
Normal file
@@ -0,0 +1,192 @@
|
||||
package rtsp
|
||||
|
||||
//#cgo LDFLAGS: -lavformat -lavutil -lavcodec -lavresample -lswscale
|
||||
// #include "ffmpeg.h"
|
||||
import "C"
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type Decoder interface {
|
||||
decode(packet *C.AVPacket, pkt *Packet) (err error)
|
||||
}
|
||||
|
||||
type decoder struct {
|
||||
index int
|
||||
codecCtx *C.AVCodecContext
|
||||
codec *C.AVCodec
|
||||
codecType int
|
||||
}
|
||||
|
||||
type Stream struct {
|
||||
formatCtx *C.AVFormatContext
|
||||
decoders []*decoder
|
||||
}
|
||||
|
||||
func newStream() (streamDecoder *Stream) {
|
||||
streamDecoder = &Stream{}
|
||||
streamDecoder.formatCtx = C.avformat_alloc_context()
|
||||
|
||||
runtime.SetFinalizer(streamDecoder, freeStream)
|
||||
return
|
||||
}
|
||||
|
||||
func freeStream(streamDecoder *Stream) {
|
||||
if streamDecoder.formatCtx != nil {
|
||||
C.avformat_free_context(streamDecoder.formatCtx)
|
||||
streamDecoder.formatCtx = nil
|
||||
}
|
||||
|
||||
for _, decoder := range streamDecoder.decoders {
|
||||
if decoder != nil {
|
||||
if decoder.codecCtx != nil {
|
||||
C.avcodec_close(decoder.codecCtx)
|
||||
C.av_free(unsafe.Pointer(decoder.codecCtx))
|
||||
decoder.codecCtx = nil
|
||||
}
|
||||
if decoder.codec != nil {
|
||||
C.av_free(unsafe.Pointer(decoder.codec))
|
||||
decoder.codec = nil
|
||||
}
|
||||
|
||||
decoder = nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func Open(url string) (streamDecoder *Stream, err error) {
|
||||
streamDecoder = newStream()
|
||||
|
||||
uri := C.CString(url)
|
||||
defer C.free(unsafe.Pointer(uri))
|
||||
|
||||
cerr := C.avformat_open_input(&streamDecoder.formatCtx, uri, nil, nil)
|
||||
if int(cerr) != 0 {
|
||||
err = fmt.Errorf("ffmpeg: avformat_open_input failed: %d", cerr)
|
||||
return
|
||||
}
|
||||
|
||||
cerr = C.avformat_find_stream_info(streamDecoder.formatCtx, nil)
|
||||
if int(cerr) != 0 {
|
||||
err = fmt.Errorf("ffmpeg: avformat_open_input failed: %d", cerr)
|
||||
return
|
||||
}
|
||||
|
||||
for i := 0; i < int(streamDecoder.formatCtx.nb_streams); i++ {
|
||||
stream := C.stream_at((*C.struct_AVFormatContext)(streamDecoder.formatCtx), C.int(i))
|
||||
|
||||
switch stream.codecpar.codec_type {
|
||||
case C.AVMEDIA_TYPE_VIDEO, C.AVMEDIA_TYPE_AUDIO:
|
||||
decoder := &decoder{index: i}
|
||||
streamDecoder.decoders = append(streamDecoder.decoders, decoder)
|
||||
decoder.codecCtx = C.avcodec_alloc_context3(nil)
|
||||
C.avcodec_parameters_to_context(decoder.codecCtx, stream.codecpar)
|
||||
decoder.codec = C.avcodec_find_decoder(decoder.codecCtx.codec_id)
|
||||
decoder.codecType = int(stream.codecpar.codec_type)
|
||||
if decoder.codec == nil {
|
||||
err = fmt.Errorf("ffmpeg: avcodec_find_decoder failed: codec %d not found", decoder.codecCtx.codec_id)
|
||||
return
|
||||
}
|
||||
|
||||
cerr = C.avcodec_open2(decoder.codecCtx, decoder.codec, nil)
|
||||
if int(cerr) != 0 {
|
||||
err = fmt.Errorf("ffmpeg: avcodec_open2 failed: %d", cerr)
|
||||
return
|
||||
}
|
||||
default:
|
||||
err = fmt.Errorf("ffmpeg: failed: codec_type %d not found", stream.codecpar.codec_type)
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (decoder *decoder) decode(packet *C.AVPacket, pkt *Packet) (err error) {
|
||||
pkt.codecType = decoder.codecType
|
||||
|
||||
// now skip audio
|
||||
if decoder.codecType == C.AVMEDIA_TYPE_AUDIO {
|
||||
return
|
||||
}
|
||||
|
||||
cerr := C.avcodec_send_packet(decoder.codecCtx, packet)
|
||||
if int(cerr) != 0 {
|
||||
err = fmt.Errorf("ffmpeg: avcodec_send_packet failed: %d", cerr)
|
||||
return
|
||||
}
|
||||
|
||||
frame := C.av_frame_alloc()
|
||||
defer C.av_frame_free(&frame)
|
||||
|
||||
cerr = C.avcodec_receive_frame(decoder.codecCtx, frame)
|
||||
if int(cerr) < 0 {
|
||||
err = fmt.Errorf("ffmpeg: avcodec_receive_frame failed: %d", cerr)
|
||||
return
|
||||
}
|
||||
|
||||
switch decoder.codecType {
|
||||
case C.AVMEDIA_TYPE_VIDEO:
|
||||
pkt.width = int(frame.width)
|
||||
pkt.height = int(frame.height)
|
||||
|
||||
encPacket := C.AVPacket{}
|
||||
defer C.av_packet_unref(&encPacket)
|
||||
|
||||
switch frame.format {
|
||||
case C.AV_PIX_FMT_NONE, C.AV_PIX_FMT_YUVJ420P:
|
||||
cerr = C.avcodec_encode_jpeg(decoder.codecCtx, frame, &encPacket)
|
||||
if cerr != C.int(0) {
|
||||
err = fmt.Errorf("ffmpeg: avcodec_encode_jpeg failed: %d", cerr)
|
||||
return
|
||||
}
|
||||
|
||||
pkt.data = make([]byte, int(packet.size))
|
||||
copy(pkt.data, *(*[]byte)(unsafe.Pointer(&encPacket.data)))
|
||||
default:
|
||||
nframe := C.av_frame_alloc()
|
||||
defer C.av_frame_free(&nframe)
|
||||
|
||||
cerr := C.avcodec_encode_jpeg_nv12(decoder.codecCtx, frame, nframe, &encPacket)
|
||||
if cerr != C.int(0) {
|
||||
err = fmt.Errorf("ffmpeg: avcodec_encode_jpeg_nv12 failed: %d", cerr)
|
||||
return
|
||||
}
|
||||
|
||||
pkt.data = make([]byte, int(encPacket.size))
|
||||
copy(pkt.data, *(*[]byte)(unsafe.Pointer(&encPacket.data)))
|
||||
}
|
||||
default:
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (streamDecoder *Stream) ReadPacket() (pkt *Packet, err error) {
|
||||
pkt = &Packet{}
|
||||
var packet C.AVPacket
|
||||
C.av_init_packet(&packet)
|
||||
|
||||
defer C.av_packet_unref(&packet)
|
||||
|
||||
cerr := C.av_read_frame(streamDecoder.formatCtx, &packet)
|
||||
if int(cerr) != 0 {
|
||||
err = fmt.Errorf("ffmpeg: av_read_frame failed: %d", cerr)
|
||||
return
|
||||
}
|
||||
|
||||
pkt.streamIndex = int(packet.stream_index)
|
||||
|
||||
for _, d := range streamDecoder.decoders {
|
||||
if d.index == int(packet.stream_index) {
|
||||
err = d.decode(&packet, pkt)
|
||||
return
|
||||
}
|
||||
}
|
||||
err = fmt.Errorf("ffmpeg: decoder not found")
|
||||
return
|
||||
}
|
||||
Reference in New Issue
Block a user