first release

This commit is contained in:
danil_e71
2021-07-21 18:43:52 +03:00
commit 7e8625fc03
10 changed files with 464 additions and 0 deletions

2
README.md Normal file
View File

@@ -0,0 +1,2 @@
# FFmpeg rtsp implementation
### need FFmpeg lib

10
example/go.mod Normal file
View 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
View 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
View 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
View 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
View 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) ;

3
go.mod Normal file
View File

@@ -0,0 +1,3 @@
module github.com/Danile71/go-rtsp
go 1.14

7
go.sum Normal file
View 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
View 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
View 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
}