27 changed files with 1366 additions and 95 deletions
@ -1,3 +1,6 @@ |
|||||||
# do not add .git, since it is needed to extract the tag |
# do not add .git, since it is needed to extract the tag |
||||||
|
|
||||||
|
/tmp |
||||||
/release |
/release |
||||||
|
/coverage*.txt |
||||||
|
/apidocs/*.html |
||||||
|
|||||||
@ -0,0 +1,94 @@ |
|||||||
|
package core |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"time" |
||||||
|
|
||||||
|
"github.com/aler9/gortsplib" |
||||||
|
"github.com/aler9/gortsplib/pkg/h264" |
||||||
|
"github.com/aler9/gortsplib/pkg/rtph264" |
||||||
|
|
||||||
|
"github.com/aler9/rtsp-simple-server/internal/logger" |
||||||
|
"github.com/aler9/rtsp-simple-server/internal/rpicamera" |
||||||
|
) |
||||||
|
|
||||||
|
type rpiCameraSourceParent interface { |
||||||
|
log(logger.Level, string, ...interface{}) |
||||||
|
sourceStaticImplSetReady(req pathSourceStaticSetReadyReq) pathSourceStaticSetReadyRes |
||||||
|
sourceStaticImplSetNotReady(req pathSourceStaticSetNotReadyReq) |
||||||
|
} |
||||||
|
|
||||||
|
type rpiCameraSource struct { |
||||||
|
params rpicamera.Params |
||||||
|
parent rpiCameraSourceParent |
||||||
|
} |
||||||
|
|
||||||
|
func newRPICameraSource( |
||||||
|
params rpicamera.Params, |
||||||
|
parent rpiCameraSourceParent, |
||||||
|
) *rpiCameraSource { |
||||||
|
return &rpiCameraSource{ |
||||||
|
params: params, |
||||||
|
parent: parent, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (s *rpiCameraSource) Log(level logger.Level, format string, args ...interface{}) { |
||||||
|
s.parent.log(level, "[rpicamera source] "+format, args...) |
||||||
|
} |
||||||
|
|
||||||
|
// run implements sourceStaticImpl.
|
||||||
|
func (s *rpiCameraSource) run(ctx context.Context) error { |
||||||
|
track := &gortsplib.TrackH264{PayloadType: 96} |
||||||
|
tracks := gortsplib.Tracks{track} |
||||||
|
enc := &rtph264.Encoder{PayloadType: 96} |
||||||
|
enc.Init() |
||||||
|
var stream *stream |
||||||
|
var start time.Time |
||||||
|
|
||||||
|
onData := func(nalus [][]byte) { |
||||||
|
if stream == nil { |
||||||
|
res := s.parent.sourceStaticImplSetReady(pathSourceStaticSetReadyReq{ |
||||||
|
tracks: tracks, |
||||||
|
}) |
||||||
|
if res.err != nil { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
s.Log(logger.Info, "ready: %s", sourceTrackInfo(tracks)) |
||||||
|
stream = res.stream |
||||||
|
start = time.Now() |
||||||
|
} |
||||||
|
|
||||||
|
pts := time.Since(start) |
||||||
|
|
||||||
|
stream.writeData(&data{ |
||||||
|
trackID: 0, |
||||||
|
ptsEqualsDTS: h264.IDRPresent(nalus), |
||||||
|
pts: pts, |
||||||
|
h264NALUs: nalus, |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
cam, err := rpicamera.New(s.params, onData) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
defer cam.Close() |
||||||
|
|
||||||
|
defer func() { |
||||||
|
if stream != nil { |
||||||
|
s.parent.sourceStaticImplSetNotReady(pathSourceStaticSetNotReadyReq{}) |
||||||
|
} |
||||||
|
}() |
||||||
|
|
||||||
|
<-ctx.Done() |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// apiSourceDescribe implements sourceStaticImpl.
|
||||||
|
func (*rpiCameraSource) apiSourceDescribe() interface{} { |
||||||
|
return struct { |
||||||
|
Type string `json:"type"` |
||||||
|
}{"rpiCameraSource"} |
||||||
|
} |
||||||
@ -0,0 +1,85 @@ |
|||||||
|
//go:build rpicamera
|
||||||
|
// +build rpicamera
|
||||||
|
|
||||||
|
package rpicamera |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"os" |
||||||
|
"os/exec" |
||||||
|
"runtime" |
||||||
|
"strconv" |
||||||
|
"time" |
||||||
|
) |
||||||
|
|
||||||
|
const ( |
||||||
|
tempPathPrefix = "/dev/shm/rtspss-embeddedexe-" |
||||||
|
) |
||||||
|
|
||||||
|
func getKernelArch() (string, error) { |
||||||
|
cmd := exec.Command("uname", "-m") |
||||||
|
|
||||||
|
byts, err := cmd.Output() |
||||||
|
if err != nil { |
||||||
|
return "", err |
||||||
|
} |
||||||
|
|
||||||
|
return string(byts[:len(byts)-1]), nil |
||||||
|
} |
||||||
|
|
||||||
|
// 32-bit embedded executables can't run on 64-bit.
|
||||||
|
func checkArch() error { |
||||||
|
if runtime.GOARCH != "arm" { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
arch, err := getKernelArch() |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
if arch == "aarch64" { |
||||||
|
return fmt.Errorf("OS is 64-bit, you need the arm64 server version") |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
type embeddedExe struct { |
||||||
|
cmd *exec.Cmd |
||||||
|
} |
||||||
|
|
||||||
|
func newEmbeddedExe(content []byte, env []string) (*embeddedExe, error) { |
||||||
|
err := checkArch() |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
tempPath := tempPathPrefix + strconv.FormatInt(time.Now().UnixNano(), 10) |
||||||
|
|
||||||
|
err = os.WriteFile(tempPath, content, 0o755) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
cmd := exec.Command(tempPath) |
||||||
|
cmd.Stdout = os.Stdout |
||||||
|
cmd.Stderr = os.Stderr |
||||||
|
cmd.Env = env |
||||||
|
|
||||||
|
err = cmd.Start() |
||||||
|
os.Remove(tempPath) |
||||||
|
|
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
return &embeddedExe{ |
||||||
|
cmd: cmd, |
||||||
|
}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (e *embeddedExe) close() { |
||||||
|
e.cmd.Process.Kill() |
||||||
|
e.cmd.Wait() |
||||||
|
} |
||||||
@ -0,0 +1,40 @@ |
|||||||
|
CC = gcc |
||||||
|
CXX = g++ |
||||||
|
|
||||||
|
CFLAGS = \
|
||||||
|
-Ofast \
|
||||||
|
-Werror \
|
||||||
|
-Wall \
|
||||||
|
-Wextra \
|
||||||
|
-Wno-unused-parameter |
||||||
|
|
||||||
|
CXXFLAGS = \
|
||||||
|
-Ofast \
|
||||||
|
-Werror \
|
||||||
|
-Wall \
|
||||||
|
-Wextra \
|
||||||
|
-Wno-unused-parameter \
|
||||||
|
-std=c++17 \
|
||||||
|
$$(pkg-config --cflags libcamera) |
||||||
|
|
||||||
|
LDFLAGS = \
|
||||||
|
-s \
|
||||||
|
-pthread \
|
||||||
|
$$(pkg-config --libs libcamera) |
||||||
|
|
||||||
|
OBJS = \
|
||||||
|
camera.o \
|
||||||
|
encoder.o \
|
||||||
|
main.o \
|
||||||
|
parameters.o |
||||||
|
|
||||||
|
all: exe |
||||||
|
|
||||||
|
%.o: %.c |
||||||
|
$(CC) $(CFLAGS) -c $< -o $@ |
||||||
|
|
||||||
|
%.o: %.cpp |
||||||
|
$(CXX) $(CXXFLAGS) -c $< -o $@ |
||||||
|
|
||||||
|
exe: $(OBJS) |
||||||
|
$(CXX) $(LDFLAGS) -o $@ $^ |
||||||
@ -0,0 +1,209 @@ |
|||||||
|
#include <stdio.h> |
||||||
|
#include <stdarg.h> |
||||||
|
#include <cstring> |
||||||
|
#include <sys/mman.h> |
||||||
|
#include <iostream> |
||||||
|
|
||||||
|
#include <libcamera/camera_manager.h> |
||||||
|
#include <libcamera/camera.h> |
||||||
|
#include <libcamera/formats.h> |
||||||
|
#include <libcamera/control_ids.h> |
||||||
|
#include <libcamera/controls.h> |
||||||
|
#include <libcamera/framebuffer_allocator.h> |
||||||
|
#include <linux/videodev2.h> |
||||||
|
|
||||||
|
#include "parameters.h" |
||||||
|
#include "camera.h" |
||||||
|
|
||||||
|
using libcamera::CameraManager; |
||||||
|
using libcamera::CameraConfiguration; |
||||||
|
using libcamera::Camera; |
||||||
|
using libcamera::StreamRoles; |
||||||
|
using libcamera::StreamRole; |
||||||
|
using libcamera::StreamConfiguration; |
||||||
|
using libcamera::Stream; |
||||||
|
using libcamera::ControlList; |
||||||
|
using libcamera::FrameBufferAllocator; |
||||||
|
using libcamera::FrameBuffer; |
||||||
|
using libcamera::Request; |
||||||
|
using libcamera::Span; |
||||||
|
|
||||||
|
namespace controls = libcamera::controls; |
||||||
|
namespace formats = libcamera::formats; |
||||||
|
|
||||||
|
char errbuf[256]; |
||||||
|
|
||||||
|
static void set_error(const char *format, ...) { |
||||||
|
va_list args; |
||||||
|
va_start(args, format); |
||||||
|
vsnprintf(errbuf, 256, format, args); |
||||||
|
} |
||||||
|
|
||||||
|
const char *camera_get_error() { |
||||||
|
return errbuf; |
||||||
|
} |
||||||
|
|
||||||
|
struct CameraPriv { |
||||||
|
parameters_t *params; |
||||||
|
camera_frame_cb frame_cb; |
||||||
|
std::unique_ptr<CameraManager> camera_manager; |
||||||
|
std::shared_ptr<Camera> camera; |
||||||
|
std::unique_ptr<FrameBufferAllocator> allocator; |
||||||
|
std::vector<std::unique_ptr<Request>> requests; |
||||||
|
}; |
||||||
|
|
||||||
|
static int get_v4l2_colorspace(std::optional<libcamera::ColorSpace> const &cs) { |
||||||
|
if (cs == libcamera::ColorSpace::Rec709) { |
||||||
|
return V4L2_COLORSPACE_REC709; |
||||||
|
} |
||||||
|
return V4L2_COLORSPACE_SMPTE170M; |
||||||
|
} |
||||||
|
|
||||||
|
bool camera_create(parameters_t *params, camera_frame_cb frame_cb, camera_t **cam) { |
||||||
|
std::unique_ptr<CameraPriv> camp = std::make_unique<CameraPriv>(); |
||||||
|
|
||||||
|
camp->camera_manager = std::make_unique<CameraManager>(); |
||||||
|
int ret = camp->camera_manager->start(); |
||||||
|
if (ret != 0) { |
||||||
|
set_error("CameraManager.start() failed"); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
std::vector<std::shared_ptr<libcamera::Camera>> cameras = camp->camera_manager->cameras(); |
||||||
|
auto rem = std::remove_if(cameras.begin(), cameras.end(), |
||||||
|
[](auto &cam) { return cam->id().find("/usb") != std::string::npos; }); |
||||||
|
cameras.erase(rem, cameras.end()); |
||||||
|
if (params->camera_id >= cameras.size()){ |
||||||
|
set_error("selected camera is not available"); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
camp->camera = camp->camera_manager->get(cameras[params->camera_id]->id()); |
||||||
|
if (camp->camera == NULL) { |
||||||
|
set_error("CameraManager.get() failed"); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
ret = camp->camera->acquire(); |
||||||
|
if (ret != 0) { |
||||||
|
set_error("Camera.acquire() failed"); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
StreamRoles stream_roles = { StreamRole::VideoRecording }; |
||||||
|
std::unique_ptr<CameraConfiguration> conf = camp->camera->generateConfiguration(stream_roles); |
||||||
|
if (conf == NULL) { |
||||||
|
set_error("Camera.generateConfiguration() failed"); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
StreamConfiguration &stream_conf = conf->at(0); |
||||||
|
stream_conf.pixelFormat = formats::YUV420; |
||||||
|
stream_conf.bufferCount = params->buffer_count; |
||||||
|
stream_conf.size.width = params->width; |
||||||
|
stream_conf.size.height = params->height; |
||||||
|
if (params->width >= 1280 || params->height >= 720) { |
||||||
|
stream_conf.colorSpace = libcamera::ColorSpace::Rec709; |
||||||
|
} else { |
||||||
|
stream_conf.colorSpace = libcamera::ColorSpace::Smpte170m; |
||||||
|
} |
||||||
|
|
||||||
|
CameraConfiguration::Status vstatus = conf->validate(); |
||||||
|
if (vstatus == CameraConfiguration::Invalid) { |
||||||
|
set_error("StreamConfiguration.validate() failed"); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
int res = camp->camera->configure(conf.get()); |
||||||
|
if (res != 0) { |
||||||
|
set_error("Camera.configure() failed"); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
Stream *stream = stream_conf.stream(); |
||||||
|
|
||||||
|
camp->allocator = std::make_unique<FrameBufferAllocator>(camp->camera); |
||||||
|
res = camp->allocator->allocate(stream); |
||||||
|
if (res < 0) { |
||||||
|
set_error("allocate() failed"); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
for (const std::unique_ptr<FrameBuffer> &buffer : camp->allocator->buffers(stream)) { |
||||||
|
std::unique_ptr<Request> request = camp->camera->createRequest((uint64_t)camp.get()); |
||||||
|
if (request == NULL) { |
||||||
|
set_error("createRequest() failed"); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
int res = request->addBuffer(stream, buffer.get()); |
||||||
|
if (res != 0) { |
||||||
|
set_error("addBuffer() failed"); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
camp->requests.push_back(std::move(request)); |
||||||
|
} |
||||||
|
|
||||||
|
camp->params = params; |
||||||
|
camp->frame_cb = frame_cb; |
||||||
|
*cam = camp.release(); |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
static void on_request_complete(Request *request) { |
||||||
|
if (request->status() == Request::RequestCancelled) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
CameraPriv *camp = (CameraPriv *)request->cookie(); |
||||||
|
|
||||||
|
FrameBuffer *buffer = request->buffers().begin()->second; |
||||||
|
|
||||||
|
int size = 0; |
||||||
|
for (const FrameBuffer::Plane &plane : buffer->planes()) { |
||||||
|
size += plane.length; |
||||||
|
} |
||||||
|
|
||||||
|
camp->frame_cb(buffer->planes()[0].fd.get(), size, buffer->metadata().timestamp / 1000); |
||||||
|
|
||||||
|
request->reuse(Request::ReuseFlag::ReuseBuffers); |
||||||
|
camp->camera->queueRequest(request); |
||||||
|
} |
||||||
|
|
||||||
|
int camera_get_stride(camera_t *cam) { |
||||||
|
CameraPriv *camp = (CameraPriv *)cam; |
||||||
|
return (*camp->camera->streams().begin())->configuration().stride; |
||||||
|
} |
||||||
|
|
||||||
|
int camera_get_colorspace(camera_t *cam) { |
||||||
|
CameraPriv *camp = (CameraPriv *)cam; |
||||||
|
return get_v4l2_colorspace((*camp->camera->streams().begin())->configuration().colorSpace); |
||||||
|
} |
||||||
|
|
||||||
|
bool camera_start(camera_t *cam) { |
||||||
|
CameraPriv *camp = (CameraPriv *)cam; |
||||||
|
|
||||||
|
ControlList ctrls = ControlList(controls::controls); |
||||||
|
int64_t frame_time = 1000000 / camp->params->fps; |
||||||
|
ctrls.set(controls::FrameDurationLimits, Span<const int64_t, 2>({ frame_time, frame_time })); |
||||||
|
|
||||||
|
int res = camp->camera->start(&ctrls); |
||||||
|
if (res != 0) { |
||||||
|
set_error("Camera.start() failed"); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
camp->camera->requestCompleted.connect(on_request_complete); |
||||||
|
|
||||||
|
for (std::unique_ptr<Request> &request : camp->requests) { |
||||||
|
int res = camp->camera->queueRequest(request.get()); |
||||||
|
if (res != 0) { |
||||||
|
set_error("Camera.queueRequest() failed"); |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
@ -0,0 +1,17 @@ |
|||||||
|
typedef void camera_t; |
||||||
|
|
||||||
|
typedef void (*camera_frame_cb)(int buffer_fd, uint64_t size, uint64_t timestamp); |
||||||
|
|
||||||
|
#ifdef __cplusplus |
||||||
|
extern "C" { |
||||||
|
#endif |
||||||
|
|
||||||
|
const char *camera_get_error(); |
||||||
|
bool camera_create(parameters_t *params, camera_frame_cb frame_cb, camera_t **cam); |
||||||
|
int camera_get_stride(camera_t *cam); |
||||||
|
int camera_get_colorspace(camera_t *cam); |
||||||
|
bool camera_start(camera_t *cam); |
||||||
|
|
||||||
|
#ifdef __cplusplus |
||||||
|
} |
||||||
|
#endif |
||||||
@ -0,0 +1,315 @@ |
|||||||
|
#include <stdbool.h> |
||||||
|
#include <stdio.h> |
||||||
|
#include <stdarg.h> |
||||||
|
#include <stdlib.h> |
||||||
|
#include <stdint.h> |
||||||
|
#include <fcntl.h> |
||||||
|
#include <unistd.h> |
||||||
|
#include <string.h> |
||||||
|
#include <sys/mman.h> |
||||||
|
#include <sys/ioctl.h> |
||||||
|
#include <errno.h> |
||||||
|
#include <poll.h> |
||||||
|
#include <pthread.h> |
||||||
|
|
||||||
|
#include <linux/videodev2.h> |
||||||
|
|
||||||
|
#include "parameters.h" |
||||||
|
#include "encoder.h" |
||||||
|
|
||||||
|
char errbuf[256]; |
||||||
|
|
||||||
|
static void set_error(const char *format, ...) { |
||||||
|
va_list args; |
||||||
|
va_start(args, format); |
||||||
|
vsnprintf(errbuf, 256, format, args); |
||||||
|
} |
||||||
|
|
||||||
|
const char *encoder_get_error() { |
||||||
|
return errbuf; |
||||||
|
} |
||||||
|
|
||||||
|
typedef struct { |
||||||
|
parameters_t *params; |
||||||
|
int fd; |
||||||
|
void **capture_buffers; |
||||||
|
int cur_buffer; |
||||||
|
encoder_output_cb output_cb; |
||||||
|
pthread_t output_thread; |
||||||
|
} encoder_priv_t; |
||||||
|
|
||||||
|
static void *output_thread(void *userdata) { |
||||||
|
encoder_priv_t *encp = (encoder_priv_t *)userdata; |
||||||
|
|
||||||
|
while (true) { |
||||||
|
struct pollfd p = { encp->fd, POLLIN, 0 }; |
||||||
|
int res = poll(&p, 1, 200); |
||||||
|
if (res == -1) { |
||||||
|
fprintf(stderr, "output_thread(): poll() failed\n"); |
||||||
|
exit(1); |
||||||
|
} |
||||||
|
|
||||||
|
if (p.revents & POLLIN) { |
||||||
|
struct v4l2_buffer buf = {0}; |
||||||
|
struct v4l2_plane planes[VIDEO_MAX_PLANES] = {0}; |
||||||
|
buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; |
||||||
|
buf.memory = V4L2_MEMORY_DMABUF; |
||||||
|
buf.length = 1; |
||||||
|
buf.m.planes = planes; |
||||||
|
int res = ioctl(encp->fd, VIDIOC_DQBUF, &buf); |
||||||
|
if (res != 0) { |
||||||
|
fprintf(stderr, "output_thread(): ioctl() failed\n"); |
||||||
|
exit(1); |
||||||
|
} |
||||||
|
|
||||||
|
memset(&buf, 0, sizeof(buf)); |
||||||
|
memset(planes, 0, sizeof(planes)); |
||||||
|
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; |
||||||
|
buf.memory = V4L2_MEMORY_MMAP; |
||||||
|
buf.length = 1; |
||||||
|
buf.m.planes = planes; |
||||||
|
res = ioctl(encp->fd, VIDIOC_DQBUF, &buf); |
||||||
|
if (res == 0) { |
||||||
|
const uint8_t *bufmem = (const uint8_t *)encp->capture_buffers[buf.index]; |
||||||
|
int bufsize = buf.m.planes[0].bytesused; |
||||||
|
encp->output_cb(bufmem, bufsize); |
||||||
|
|
||||||
|
int index = buf.index; |
||||||
|
int length = buf.m.planes[0].length; |
||||||
|
|
||||||
|
struct v4l2_buffer buf = {0}; |
||||||
|
struct v4l2_plane planes[VIDEO_MAX_PLANES] = {0}; |
||||||
|
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; |
||||||
|
buf.memory = V4L2_MEMORY_MMAP; |
||||||
|
buf.index = index; |
||||||
|
buf.length = 1; |
||||||
|
buf.m.planes = planes; |
||||||
|
buf.m.planes[0].bytesused = 0; |
||||||
|
buf.m.planes[0].length = length; |
||||||
|
int res = ioctl(encp->fd, VIDIOC_QBUF, &buf); |
||||||
|
if (res < 0) { |
||||||
|
fprintf(stderr, "output_thread(): ioctl() failed\n"); |
||||||
|
exit(1); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return NULL; |
||||||
|
} |
||||||
|
|
||||||
|
bool encoder_create(parameters_t *params, int stride, int colorspace, encoder_output_cb output_cb, encoder_t **enc) { |
||||||
|
*enc = malloc(sizeof(encoder_priv_t)); |
||||||
|
encoder_priv_t *encp = (encoder_priv_t *)(*enc); |
||||||
|
|
||||||
|
encp->fd = open("/dev/video11", O_RDWR, 0); |
||||||
|
if (encp->fd < 0) { |
||||||
|
set_error("unable to open device"); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
struct v4l2_control ctrl = {0}; |
||||||
|
ctrl.id = V4L2_CID_MPEG_VIDEO_BITRATE; |
||||||
|
ctrl.value = params->bitrate; |
||||||
|
int res = ioctl(encp->fd, VIDIOC_S_CTRL, &ctrl); |
||||||
|
if (res != 0) { |
||||||
|
set_error("unable to set bitrate"); |
||||||
|
close(encp->fd); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
ctrl.id = V4L2_CID_MPEG_VIDEO_H264_PROFILE; |
||||||
|
ctrl.value = params->profile; |
||||||
|
res = ioctl(encp->fd, VIDIOC_S_CTRL, &ctrl); |
||||||
|
if (res != 0) { |
||||||
|
set_error("unable to set profile"); |
||||||
|
close(encp->fd); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
ctrl.id = V4L2_CID_MPEG_VIDEO_H264_LEVEL; |
||||||
|
ctrl.value = params->level; |
||||||
|
res = ioctl(encp->fd, VIDIOC_S_CTRL, &ctrl); |
||||||
|
if (res != 0) { |
||||||
|
set_error("unable to set level"); |
||||||
|
close(encp->fd); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
ctrl.id = V4L2_CID_MPEG_VIDEO_H264_I_PERIOD; |
||||||
|
ctrl.value = params->idr_period; |
||||||
|
res = ioctl(encp->fd, VIDIOC_S_CTRL, &ctrl); |
||||||
|
if (res != 0) { |
||||||
|
set_error("unable to set IDR period"); |
||||||
|
close(encp->fd); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
ctrl.id = V4L2_CID_MPEG_VIDEO_REPEAT_SEQ_HEADER; |
||||||
|
ctrl.value = 0; |
||||||
|
res = ioctl(encp->fd, VIDIOC_S_CTRL, &ctrl); |
||||||
|
if (res != 0) { |
||||||
|
set_error("unable to set REPEAT_SEQ_HEADER"); |
||||||
|
close(encp->fd); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
struct v4l2_format fmt = {0}; |
||||||
|
fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; |
||||||
|
fmt.fmt.pix_mp.width = params->width; |
||||||
|
fmt.fmt.pix_mp.height = params->height; |
||||||
|
fmt.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_YUV420; |
||||||
|
fmt.fmt.pix_mp.plane_fmt[0].bytesperline = stride; |
||||||
|
fmt.fmt.pix_mp.field = V4L2_FIELD_ANY; |
||||||
|
fmt.fmt.pix_mp.colorspace = colorspace; |
||||||
|
fmt.fmt.pix_mp.num_planes = 1; |
||||||
|
res = ioctl(encp->fd, VIDIOC_S_FMT, &fmt); |
||||||
|
if (res != 0) { |
||||||
|
set_error("unable to set output format"); |
||||||
|
close(encp->fd); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
memset(&fmt, 0, sizeof(fmt)); |
||||||
|
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; |
||||||
|
fmt.fmt.pix_mp.width = params->width; |
||||||
|
fmt.fmt.pix_mp.height = params->height; |
||||||
|
fmt.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_H264; |
||||||
|
fmt.fmt.pix_mp.field = V4L2_FIELD_ANY; |
||||||
|
fmt.fmt.pix_mp.colorspace = V4L2_COLORSPACE_DEFAULT; |
||||||
|
fmt.fmt.pix_mp.num_planes = 1; |
||||||
|
fmt.fmt.pix_mp.plane_fmt[0].bytesperline = 0; |
||||||
|
fmt.fmt.pix_mp.plane_fmt[0].sizeimage = 512 << 10; |
||||||
|
res = ioctl(encp->fd, VIDIOC_S_FMT, &fmt); |
||||||
|
if (res != 0) { |
||||||
|
set_error("unable to set capture format"); |
||||||
|
close(encp->fd); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
struct v4l2_streamparm parm = {0}; |
||||||
|
parm.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; |
||||||
|
parm.parm.output.timeperframe.numerator = 1; |
||||||
|
parm.parm.output.timeperframe.denominator = params->fps; |
||||||
|
res = ioctl(encp->fd, VIDIOC_S_PARM, &parm); |
||||||
|
if (res != 0) { |
||||||
|
set_error("unable to set fps"); |
||||||
|
close(encp->fd); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
struct v4l2_requestbuffers reqbufs = {0}; |
||||||
|
reqbufs.count = params->buffer_count; |
||||||
|
reqbufs.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; |
||||||
|
reqbufs.memory = V4L2_MEMORY_DMABUF; |
||||||
|
res = ioctl(encp->fd, VIDIOC_REQBUFS, &reqbufs); |
||||||
|
if (res != 0) { |
||||||
|
set_error("unable to set output buffers"); |
||||||
|
close(encp->fd); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
memset(&reqbufs, 0, sizeof(reqbufs)); |
||||||
|
reqbufs.count = params->capture_buffer_count; |
||||||
|
reqbufs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; |
||||||
|
reqbufs.memory = V4L2_MEMORY_MMAP; |
||||||
|
res = ioctl(encp->fd, VIDIOC_REQBUFS, &reqbufs); |
||||||
|
if (res != 0) { |
||||||
|
set_error("unable to set capture buffers"); |
||||||
|
close(encp->fd); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
encp->capture_buffers = malloc(sizeof(void *) * reqbufs.count); |
||||||
|
|
||||||
|
for (unsigned int i = 0; i < reqbufs.count; i++) { |
||||||
|
struct v4l2_plane planes[VIDEO_MAX_PLANES]; |
||||||
|
|
||||||
|
struct v4l2_buffer buffer = {0}; |
||||||
|
buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; |
||||||
|
buffer.memory = V4L2_MEMORY_MMAP; |
||||||
|
buffer.index = i; |
||||||
|
buffer.length = 1; |
||||||
|
buffer.m.planes = planes; |
||||||
|
int res = ioctl(encp->fd, VIDIOC_QUERYBUF, &buffer); |
||||||
|
if (res != 0) { |
||||||
|
set_error("unable to query buffer"); |
||||||
|
free(encp->capture_buffers); |
||||||
|
close(encp->fd); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
encp->capture_buffers[i] = mmap( |
||||||
|
0, |
||||||
|
buffer.m.planes[0].length, |
||||||
|
PROT_READ | PROT_WRITE, MAP_SHARED, |
||||||
|
encp->fd, |
||||||
|
buffer.m.planes[0].m.mem_offset); |
||||||
|
if (encp->capture_buffers[i] == MAP_FAILED) { |
||||||
|
set_error("mmap() failed"); |
||||||
|
free(encp->capture_buffers); |
||||||
|
close(encp->fd); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
res = ioctl(encp->fd, VIDIOC_QBUF, &buffer); |
||||||
|
if (res != 0) { |
||||||
|
set_error("ioctl() failed"); |
||||||
|
free(encp->capture_buffers); |
||||||
|
close(encp->fd); |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; |
||||||
|
res = ioctl(encp->fd, VIDIOC_STREAMON, &type); |
||||||
|
if (res != 0) { |
||||||
|
set_error("unable to activate output stream"); |
||||||
|
free(encp->capture_buffers); |
||||||
|
close(encp->fd); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; |
||||||
|
res = ioctl(encp->fd, VIDIOC_STREAMON, &type); |
||||||
|
if (res != 0) { |
||||||
|
set_error("unable to activate capture stream"); |
||||||
|
free(encp->capture_buffers); |
||||||
|
close(encp->fd); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
encp->params = params; |
||||||
|
encp->cur_buffer = 0; |
||||||
|
encp->output_cb = output_cb; |
||||||
|
|
||||||
|
pthread_create(&encp->output_thread, NULL, output_thread, encp); |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
void encoder_encode(encoder_t *enc, int buffer_fd, size_t size, int64_t timestamp_us) { |
||||||
|
encoder_priv_t *encp = (encoder_priv_t *)enc; |
||||||
|
|
||||||
|
int index = encp->cur_buffer++; |
||||||
|
encp->cur_buffer %= encp->params->buffer_count; |
||||||
|
|
||||||
|
struct v4l2_buffer buf = {0}; |
||||||
|
struct v4l2_plane planes[VIDEO_MAX_PLANES] = {0}; |
||||||
|
buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; |
||||||
|
buf.index = index; |
||||||
|
buf.field = V4L2_FIELD_NONE; |
||||||
|
buf.memory = V4L2_MEMORY_DMABUF; |
||||||
|
buf.length = 1; |
||||||
|
buf.timestamp.tv_sec = timestamp_us / 1000000; |
||||||
|
buf.timestamp.tv_usec = timestamp_us % 1000000; |
||||||
|
buf.m.planes = planes; |
||||||
|
buf.m.planes[0].m.fd = buffer_fd; |
||||||
|
buf.m.planes[0].bytesused = size; |
||||||
|
buf.m.planes[0].length = size; |
||||||
|
int res = ioctl(encp->fd, VIDIOC_QBUF, &buf); |
||||||
|
if (res != 0) { |
||||||
|
fprintf(stderr, "encoder_encode(): ioctl() failed\n"); |
||||||
|
exit(1); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,7 @@ |
|||||||
|
typedef void encoder_t; |
||||||
|
|
||||||
|
typedef void (*encoder_output_cb)(const uint8_t *buf, uint64_t size); |
||||||
|
|
||||||
|
const char *encoder_get_error(); |
||||||
|
bool encoder_create(parameters_t *params, int stride, int colorspace, encoder_output_cb output_cb, encoder_t **enc); |
||||||
|
void encoder_encode(encoder_t *enc, int buffer_fd, size_t size, int64_t timestamp_us); |
||||||
@ -0,0 +1,117 @@ |
|||||||
|
#include <stdio.h> |
||||||
|
#include <stdbool.h> |
||||||
|
#include <stdarg.h> |
||||||
|
#include <stdint.h> |
||||||
|
#include <stdlib.h> |
||||||
|
#include <fcntl.h> |
||||||
|
#include <unistd.h> |
||||||
|
#include <signal.h> |
||||||
|
#include <string.h> |
||||||
|
#include <pthread.h> |
||||||
|
|
||||||
|
#include "parameters.h" |
||||||
|
#include "camera.h" |
||||||
|
#include "encoder.h" |
||||||
|
|
||||||
|
int pipe_fd; |
||||||
|
pthread_mutex_t pipe_mutex; |
||||||
|
parameters_t params; |
||||||
|
camera_t *cam; |
||||||
|
encoder_t *enc; |
||||||
|
|
||||||
|
static void pipe_write_error(int fd, const char *format, ...) { |
||||||
|
char buf[256]; |
||||||
|
buf[0] = 'e'; |
||||||
|
va_list args; |
||||||
|
va_start(args, format); |
||||||
|
vsnprintf(&buf[1], 255, format, args); |
||||||
|
int n = strlen(buf); |
||||||
|
write(fd, &n, 4); |
||||||
|
write(fd, buf, n); |
||||||
|
} |
||||||
|
|
||||||
|
static void pipe_write_ready(int fd) { |
||||||
|
char buf[] = {'r'}; |
||||||
|
int n = 1; |
||||||
|
write(fd, &n, 4); |
||||||
|
write(fd, buf, n); |
||||||
|
} |
||||||
|
|
||||||
|
static void pipe_write_buf(int fd, const uint8_t *buf, int n) { |
||||||
|
char head[] = {'b'}; |
||||||
|
n++; |
||||||
|
write(fd, &n, 4); |
||||||
|
write(fd, head, 1); |
||||||
|
write(fd, buf, n-1); |
||||||
|
} |
||||||
|
|
||||||
|
static void on_frame(int buffer_fd, uint64_t size, uint64_t timestamp) { |
||||||
|
encoder_encode(enc, buffer_fd, size, timestamp); |
||||||
|
} |
||||||
|
|
||||||
|
static void on_encoder_output(const uint8_t *buf, uint64_t size) { |
||||||
|
pthread_mutex_lock(&pipe_mutex); |
||||||
|
pipe_write_buf(pipe_fd, buf, size); |
||||||
|
pthread_mutex_unlock(&pipe_mutex); |
||||||
|
} |
||||||
|
|
||||||
|
static bool init_siglistener(sigset_t *set) { |
||||||
|
sigemptyset(set); |
||||||
|
|
||||||
|
int res = sigaddset(set, SIGKILL); |
||||||
|
if (res == -1) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
int main() { |
||||||
|
pipe_fd = atoi(getenv("PIPE_FD")); |
||||||
|
|
||||||
|
pthread_mutex_init(&pipe_mutex, NULL); |
||||||
|
pthread_mutex_lock(&pipe_mutex); |
||||||
|
|
||||||
|
parameters_load(¶ms); |
||||||
|
|
||||||
|
bool ok = camera_create( |
||||||
|
¶ms, |
||||||
|
on_frame, |
||||||
|
&cam); |
||||||
|
if (!ok) { |
||||||
|
pipe_write_error(pipe_fd, "camera_create(): %s", camera_get_error()); |
||||||
|
return 5; |
||||||
|
} |
||||||
|
|
||||||
|
ok = encoder_create( |
||||||
|
¶ms, |
||||||
|
camera_get_stride(cam), |
||||||
|
camera_get_colorspace(cam), |
||||||
|
on_encoder_output, |
||||||
|
&enc); |
||||||
|
if (!ok) { |
||||||
|
pipe_write_error(pipe_fd, "encoder_create(): %s", encoder_get_error()); |
||||||
|
return 5; |
||||||
|
} |
||||||
|
|
||||||
|
ok = camera_start(cam); |
||||||
|
if (!ok) { |
||||||
|
pipe_write_error(pipe_fd, "camera_start(): %s", camera_get_error()); |
||||||
|
return 5; |
||||||
|
} |
||||||
|
|
||||||
|
sigset_t set; |
||||||
|
ok = init_siglistener(&set); |
||||||
|
if (!ok) { |
||||||
|
pipe_write_error(pipe_fd, "init_siglistener() failed"); |
||||||
|
return 5; |
||||||
|
} |
||||||
|
|
||||||
|
pipe_write_ready(pipe_fd); |
||||||
|
pthread_mutex_unlock(&pipe_mutex); |
||||||
|
|
||||||
|
int sig; |
||||||
|
sigwait(&set, &sig); |
||||||
|
|
||||||
|
return 0; |
||||||
|
} |
||||||
@ -0,0 +1,36 @@ |
|||||||
|
#include <stdlib.h> |
||||||
|
#include <string.h> |
||||||
|
|
||||||
|
#include <linux/videodev2.h> |
||||||
|
|
||||||
|
#include "parameters.h" |
||||||
|
|
||||||
|
void parameters_load(parameters_t *params) { |
||||||
|
params->camera_id = atoi(getenv("CAMERA_ID")); |
||||||
|
params->width = atoi(getenv("WIDTH")); |
||||||
|
params->height = atoi(getenv("HEIGHT")); |
||||||
|
params->fps = atoi(getenv("FPS")); |
||||||
|
params->idr_period = atoi(getenv("IDR_PERIOD")); |
||||||
|
params->bitrate = atoi(getenv("BITRATE")); |
||||||
|
|
||||||
|
const char *profile = getenv("PROFILE"); |
||||||
|
if (strcmp(profile, "baseline") == 0) { |
||||||
|
params->profile = V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE; |
||||||
|
} else if (strcmp(profile, "main") == 0) { |
||||||
|
params->profile = V4L2_MPEG_VIDEO_H264_PROFILE_MAIN; |
||||||
|
} else { |
||||||
|
params->profile = V4L2_MPEG_VIDEO_H264_PROFILE_HIGH; |
||||||
|
} |
||||||
|
|
||||||
|
const char *level = getenv("LEVEL"); |
||||||
|
if (strcmp(level, "4.0") == 0) { |
||||||
|
params->level = V4L2_MPEG_VIDEO_H264_LEVEL_4_0; |
||||||
|
} else if (strcmp(level, "4.1") == 0) { |
||||||
|
params->level = V4L2_MPEG_VIDEO_H264_LEVEL_4_1; |
||||||
|
} else { |
||||||
|
params->level = V4L2_MPEG_VIDEO_H264_LEVEL_4_2; |
||||||
|
} |
||||||
|
|
||||||
|
params->buffer_count = 3; |
||||||
|
params->capture_buffer_count = params->buffer_count * 2; |
||||||
|
} |
||||||
@ -0,0 +1,22 @@ |
|||||||
|
typedef struct { |
||||||
|
unsigned int camera_id; |
||||||
|
unsigned int width; |
||||||
|
unsigned int height; |
||||||
|
unsigned int fps; |
||||||
|
unsigned int idr_period; |
||||||
|
unsigned int bitrate; |
||||||
|
unsigned int profile; |
||||||
|
unsigned int level; |
||||||
|
unsigned int buffer_count; |
||||||
|
unsigned int capture_buffer_count; |
||||||
|
} parameters_t; |
||||||
|
|
||||||
|
#ifdef __cplusplus |
||||||
|
extern "C" { |
||||||
|
#endif |
||||||
|
|
||||||
|
void parameters_load(parameters_t *params); |
||||||
|
|
||||||
|
#ifdef __cplusplus |
||||||
|
} |
||||||
|
#endif |
||||||
@ -0,0 +1,13 @@ |
|||||||
|
package rpicamera |
||||||
|
|
||||||
|
// Params is a set of camera parameters.
|
||||||
|
type Params struct { |
||||||
|
CameraID int |
||||||
|
Width int |
||||||
|
Height int |
||||||
|
FPS int |
||||||
|
IDRPeriod int |
||||||
|
Bitrate int |
||||||
|
Profile string |
||||||
|
Level string |
||||||
|
} |
||||||
@ -0,0 +1,69 @@ |
|||||||
|
//go:build rpicamera
|
||||||
|
// +build rpicamera
|
||||||
|
|
||||||
|
package rpicamera |
||||||
|
|
||||||
|
import ( |
||||||
|
"encoding/binary" |
||||||
|
"syscall" |
||||||
|
) |
||||||
|
|
||||||
|
func syscallReadAll(fd int, buf []byte) error { |
||||||
|
size := len(buf) |
||||||
|
read := 0 |
||||||
|
|
||||||
|
for { |
||||||
|
n, err := syscall.Read(fd, buf[read:size]) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
read += n |
||||||
|
if read >= size { |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
type pipe struct { |
||||||
|
readFD int |
||||||
|
writeFD int |
||||||
|
} |
||||||
|
|
||||||
|
func newPipe() (*pipe, error) { |
||||||
|
fds := make([]int, 2) |
||||||
|
err := syscall.Pipe(fds) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
return &pipe{ |
||||||
|
readFD: fds[0], |
||||||
|
writeFD: fds[1], |
||||||
|
}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (p *pipe) close() { |
||||||
|
syscall.Close(p.readFD) |
||||||
|
syscall.Close(p.writeFD) |
||||||
|
} |
||||||
|
|
||||||
|
func (p *pipe) read() ([]byte, error) { |
||||||
|
sizebuf := make([]byte, 4) |
||||||
|
err := syscallReadAll(p.readFD, sizebuf) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
size := int(binary.LittleEndian.Uint32(sizebuf)) |
||||||
|
buf := make([]byte, size) |
||||||
|
|
||||||
|
err = syscallReadAll(p.readFD, buf) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
return buf, nil |
||||||
|
} |
||||||
@ -0,0 +1,134 @@ |
|||||||
|
//go:build rpicamera
|
||||||
|
// +build rpicamera
|
||||||
|
|
||||||
|
package rpicamera |
||||||
|
|
||||||
|
import ( |
||||||
|
_ "embed" |
||||||
|
"fmt" |
||||||
|
"strconv" |
||||||
|
|
||||||
|
"github.com/aler9/gortsplib/pkg/h264" |
||||||
|
) |
||||||
|
|
||||||
|
//go:embed exe/exe
|
||||||
|
var exeContent []byte |
||||||
|
|
||||||
|
type RPICamera struct { |
||||||
|
onData func([][]byte) |
||||||
|
|
||||||
|
exe *embeddedExe |
||||||
|
pipe *pipe |
||||||
|
|
||||||
|
waitDone chan error |
||||||
|
readerDone chan error |
||||||
|
} |
||||||
|
|
||||||
|
func New( |
||||||
|
params Params, |
||||||
|
onData func([][]byte), |
||||||
|
) (*RPICamera, error) { |
||||||
|
pipe, err := newPipe() |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
env := []string{ |
||||||
|
"PIPE_FD=" + strconv.FormatInt(int64(pipe.writeFD), 10), |
||||||
|
"CAMERA_ID=" + strconv.FormatInt(int64(params.CameraID), 10), |
||||||
|
"WIDTH=" + strconv.FormatInt(int64(params.Width), 10), |
||||||
|
"HEIGHT=" + strconv.FormatInt(int64(params.Height), 10), |
||||||
|
"FPS=" + strconv.FormatInt(int64(params.FPS), 10), |
||||||
|
"IDR_PERIOD=" + strconv.FormatInt(int64(params.IDRPeriod), 10), |
||||||
|
"BITRATE=" + strconv.FormatInt(int64(params.Bitrate), 10), |
||||||
|
"PROFILE=" + params.Profile, |
||||||
|
"LEVEL=" + params.Level, |
||||||
|
} |
||||||
|
|
||||||
|
exe, err := newEmbeddedExe(exeContent, env) |
||||||
|
if err != nil { |
||||||
|
pipe.close() |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
waitDone := make(chan error) |
||||||
|
go func() { |
||||||
|
waitDone <- exe.cmd.Wait() |
||||||
|
}() |
||||||
|
|
||||||
|
readerDone := make(chan error) |
||||||
|
go func() { |
||||||
|
readerDone <- func() error { |
||||||
|
buf, err := pipe.read() |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
switch buf[0] { |
||||||
|
case 'e': |
||||||
|
return fmt.Errorf(string(buf[1:])) |
||||||
|
|
||||||
|
case 'r': |
||||||
|
return nil |
||||||
|
|
||||||
|
default: |
||||||
|
return fmt.Errorf("unexpected output from pipe (%c)", buf[0]) |
||||||
|
} |
||||||
|
}() |
||||||
|
}() |
||||||
|
|
||||||
|
select { |
||||||
|
case <-waitDone: |
||||||
|
exe.close() |
||||||
|
pipe.close() |
||||||
|
<-readerDone |
||||||
|
return nil, fmt.Errorf("process exited unexpectedly") |
||||||
|
|
||||||
|
case err := <-readerDone: |
||||||
|
if err != nil { |
||||||
|
exe.close() |
||||||
|
<-waitDone |
||||||
|
pipe.close() |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
readerDone = make(chan error) |
||||||
|
go func() { |
||||||
|
readerDone <- func() error { |
||||||
|
for { |
||||||
|
buf, err := pipe.read() |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
if buf[0] != 'b' { |
||||||
|
return fmt.Errorf("unexpected output from pipe (%c)", buf[0]) |
||||||
|
} |
||||||
|
buf = buf[1:] |
||||||
|
|
||||||
|
nalus, err := h264.AnnexBUnmarshal(buf) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
onData(nalus) |
||||||
|
} |
||||||
|
}() |
||||||
|
}() |
||||||
|
|
||||||
|
return &RPICamera{ |
||||||
|
onData: onData, |
||||||
|
exe: exe, |
||||||
|
pipe: pipe, |
||||||
|
waitDone: waitDone, |
||||||
|
readerDone: readerDone, |
||||||
|
}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (c *RPICamera) Close() { |
||||||
|
c.exe.close() |
||||||
|
<-c.waitDone |
||||||
|
c.pipe.close() |
||||||
|
<-c.readerDone |
||||||
|
} |
||||||
@ -0,0 +1,23 @@ |
|||||||
|
//go:build !rpicamera
|
||||||
|
// +build !rpicamera
|
||||||
|
|
||||||
|
package rpicamera |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
) |
||||||
|
|
||||||
|
// RPICamera is a RPI Camera reader.
|
||||||
|
type RPICamera struct{} |
||||||
|
|
||||||
|
// New allocates a RPICamera.
|
||||||
|
func New( |
||||||
|
params Params, |
||||||
|
onData func([][]byte), |
||||||
|
) (*RPICamera, error) { |
||||||
|
return nil, fmt.Errorf("server was compiled without support for the Raspberry Pi Camera") |
||||||
|
} |
||||||
|
|
||||||
|
// Close closes a RPICamera.
|
||||||
|
func (c *RPICamera) Close() { |
||||||
|
} |
||||||
Loading…
Reference in new issue