* update rtmp & rtmp demo

This commit is contained in:
lxowalle
2024-05-29 19:30:45 +08:00
parent 5a3ff56692
commit 985900089b
11 changed files with 512 additions and 19 deletions

View File

@@ -129,6 +129,12 @@ namespace maix::sys
*/
std::vector<std::map<std::string, std::string>> disk_partitions(bool only_disk = true);
/**
* register default signal handle
* @maixpy maix.sys.register_default_signal_handle
*/
void register_default_signal_handle();
/**
* Power off device
* @maixpy maix.sys.poweroff

View File

@@ -0,0 +1,22 @@
#include "maix_util.hpp"
#include "maix_app.hpp"
#include "signal.h"
namespace maix::sys
{
static void signal_handle(int signal)
{
const char *signal_msg = NULL;
switch (signal) {
case SIGINT:
maix::app::set_exit_flag(true);
raise(SIGINT);
break;
default: signal_msg = "UNKNOWN"; break;
}
}
void register_default_signal_handle() {
signal(SIGILL, signal_handle);
}
}

View File

@@ -0,0 +1,22 @@
#include "maix_sys.hpp"
#include "maix_app.hpp"
#include "signal.h"
namespace maix::sys
{
static void signal_handle(int signal)
{
const char *signal_msg = NULL;
switch (signal) {
case SIGINT:
maix::app::set_exit_flag(true);
raise(SIGINT);
break;
default: signal_msg = "UNKNOWN"; break;
}
}
void register_default_signal_handle() {
signal(SIGILL, signal_handle);
}
}

View File

@@ -17,7 +17,11 @@ if(CONFIG_MAIXCAM_LIB_COMPILE_FROM_SOURCE)
append_srcs_dir(ADD_SRCS "${source_dir}/nn")
list(APPEND ADD_REQUIREMENTS cvi_tpu)
# media_server
append_srcs_dir(ADD_SRCS "${source_dir}/media_server")
list(APPEND ADD_REQUIREMENTS media_server)
# middleware
set(middleware_include_dir .
${middleware_src_path}/v2/component/panel/${CONFIG_SOPHGO_MIDDLEWARE_CHIP}
${middleware_src_path}/v2/include

View File

@@ -0,0 +1,23 @@
#ifndef __MAIX_AVC2FLV
#define __MAIX_AVC2FLV
#ifdef __cplusplus
extern "C" {
#endif
#include "stdint.h"
int maix_avc2flv_init(int max_buff_size);
int maix_avc2flv_deinit();
int maix_avc2flv_prepare(uint8_t *data, int data_size);
int maix_avc2flv_iterate(void **nalu, int *size);
int maix_avc2flv(void *nalu, int nalu_size, uint32_t pts, uint32_t dts, uint8_t **flv, int *flv_size);
// need free data after used
int maix_flv_get_tail(uint8_t **data, int *size);
// need free data after used
int maix_flv_get_header(int audio, int video, uint8_t **data, int *size);
#ifdef __cplusplus
}
#endif
#endif // __MAIX_AVC2FLV

View File

@@ -10,12 +10,29 @@
#include "maix_basic.hpp"
#include "maix_camera.hpp"
#include "maix_video.hpp"
#include <string>
#include <stdexcept>
#include <pthread.h>
/**
* @brief maix.rtmp module
* @maixpy maix.rtmp
*/
namespace maix::rtmp
{
/**
* Video type
* @maixpy maix.rtmp.TagType
*/
enum TagType
{
TAG_NONE,
TAG_VIDEO,
TAG_AUDIO,
TAG_SCRIPT,
};
/**
* Rtmp class
* @maixpy maix.rtmp.Rtmp
@@ -25,15 +42,20 @@ namespace maix::rtmp
std::string _app;
std::string _stream;
int _port;
int _bitrate;
int _socket;
void *_handler;
camera::Camera *_cam;
camera::Camera *_camera;
thread::Thread *_thread;
thread::Thread *_push_thread;
thread::Thread *_app_thread;
pthread_mutex_t _lock;
bool _start;
std::string _path;
image::Image *_capture_image;
bool _need_capture;
public:
/**
* @brief Construct a new Video object
@@ -45,29 +67,39 @@ namespace maix::rtmp
* @param port rtmp port, default is 1935.
* @param app rtmp app name
* @param stream rtmp stream name
* @param bitrate rtmp bitrate, default is 1000 * 1000
* @maixpy maix.rtmp.Rtmp.__init__
* @maixcdk maix.rtmp.Rtmp.Rtmp
*/
Rtmp(std::string host, int port = 1935, std::string app = std::string(), std::string stream = std::string());
Rtmp(std::string host = "localhost", int port = 1935, std::string app = std::string(), std::string stream = std::string(), int bitrate = 1000 * 1000);
~Rtmp();
/**
* @brief Get bitrate
* @return bitrate
* @maixpy maix.rtmp.Rtmp.push_video
*/
int bitrate() {
return _bitrate;
}
/**
* @brief Push rtmp video data
* @return error code, err::ERR_NONE means success, others means failed
* @return return 0 ok, other error
* @maixcdk maix.rtmp.Rtmp.push_video
*/
int push_video(void *data, size_t data_size, uint32_t timestamp);
/**
* @brief Push rtmp audio data
* @return error code, err::ERR_NONE means success, others means failed
* @return return 0 ok, other error
* @maixcdk maix.rtmp.Rtmp.push_audio
*/
int push_audio(void *data, size_t data_size, uint32_t timestamp);
/**
* @brief Push rtmp script data
* @return error code, err::ERR_NONE means success, others means failed
* @return return 0 ok, other error
* @maixcdk maix.rtmp.Rtmp.push_script
*/
int push_script(void *data, size_t data_size, uint32_t timestamp);
@@ -80,10 +112,56 @@ namespace maix::rtmp
* @maixpy maix.rtmp.Rtmp.bind_camera
*/
err::Err bind_camera(camera::Camera *cam) {
_cam = cam;
_camera = cam;
return err::ERR_NONE;
}
/**
* @brief If you bind a camera, return the camera object.
* @return Camera object
* @maixpy maix.rtmp.Rtmp.get_camera
*/
camera::Camera *get_camera() {
return _camera;
}
/**
* @brief If you bind a camera, capture the image of the camera
* @note The return value may be null, you must check whether the return value is null
* @return Image object
* @maixcdk maix.rtmp.Rtmp.capture
*/
image::Image *capture() {
err::check_raise(err::ERR_NOT_IMPL, "not support now!");
// lock(300);
// if (!_capture_image || !_capture_image->data()) {
// unlock();
// return NULL;
// }
// image::Image *new_image = new image::Image(_capture_image->width(), _capture_image->height(), _capture_image->format(),
// (uint8_t *)_capture_image->data(), _capture_image->data_size(), false);
// unlock();
// return new_image;
return NULL;
}
/**
* @brief Get pointer of capture image
* @return Image object
*/
image::Image *get_capture_image() {
return _capture_image;
}
/**
* @brief Set pointer of capture image
* @return Image object
*/
image::Image *set_capture_image(void *img) {
_capture_image = (image::Image *)img;
return _capture_image;
}
/**
* @brief Start push stream
* @note only support flv file now
@@ -94,7 +172,7 @@ namespace maix::rtmp
err::Err start(std::string path = std::string());
/**
* @brief Stop push
* @brief Stop push stream
* @return error code, err::ERR_NONE means success, others means failed
* @maixpy maix.rtmp.Rtmp.stop
*/
@@ -132,6 +210,15 @@ namespace maix::rtmp
bool is_started() {
return _start ? true : false;
}
/**
* @brief get handler
* @note DO NOT ADD TO MAIXPY
* @return rtmp handler
*/
void *get_handler() {
return _handler;
}
};
}

View File

@@ -69,6 +69,7 @@ namespace maix::camera
this->width = width;
this->height = height;
this->buffer_num = buff_num;
this->ch = -1;
if (0 != mmf_init()) {
err::check_raise(err::ERR_RUNTIME, "mmf init failed");

View File

@@ -1,17 +1,27 @@
#include "maix_rtmp.hpp"
#include "maix_video.hpp"
#include "sophgo_middleware.hpp"
#include <stdio.h>
#include "sockutil.h"
#include "sys/system.h"
#include "rtmp-client.h"
#include "flv-reader.h"
#include "flv-writer.h"
#include "flv-header.h"
#include "flv-proto.h"
#include "maix_avc2flv.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#define MMF_VENC_CHN 1
#define AAC_ADTS_HEADER_SIZE 7
#define FLV_TAG_HEAD_LEN 11
#define FLV_PRE_TAG_LEN 4
static int rtmp_client_send(void* param, const void* header, size_t len, const void* data, size_t bytes)
{
socket_t* socket = (socket_t*)param;
@@ -22,12 +32,74 @@ static int rtmp_client_send(void* param, const void* header, size_t len, const v
return socket_send_v_all_by_time(*socket, vec, bytes > 0 ? 2 : 1, 0, 5000);
}
static uint8_t* h264_startcode(uint8_t* data, size_t bytes)
{
size_t i;
for (i = 2; i + 1 < bytes; i++)
{
if (0x01 == data[i] && 0x00 == data[i - 1] && 0x00 == data[i - 2])
return data + i + 1;
}
return NULL;
}
int maix_rtmp_client_push_h264(rtmp_client_t *client, struct flv_vec_t *vec, int num, uint32_t timestamp)
{
int tmp_offset = 0;
int tmp_max_size = 256 * 1024;
uint8_t *tmp = (uint8_t *)malloc(tmp_max_size);
if (!tmp) return -1;
for (int i = 0; i < num; i ++) {
uint8_t *nalu = h264_startcode((uint8_t *)vec[i].ptr, vec[i].len);
uint8_t *raw = (uint8_t *)vec[i].ptr;
int raw_size = (uint8_t *)vec[i].ptr + vec[i].len - raw;
uint8_t nalutype = nalu[0] & 0x1f;
if (nalutype == 0x7 || nalutype == 0x8 || nalutype == 6) {
if (tmp_offset + raw_size > tmp_max_size) return -1;
memcpy(tmp + tmp_offset, raw, raw_size);
tmp_offset += raw_size;
} else if (nalutype == 0x5 || nalutype == 0x1) {
if (tmp_offset + raw_size > tmp_max_size) return -1;
memcpy(tmp + tmp_offset, raw, raw_size);
tmp_offset += raw_size;
uint8_t *flv;
int flv_size;
if (0 != maix_avc2flv(tmp, tmp_offset, timestamp, timestamp, &flv, &flv_size)) {
printf("maix_avc2flv failed!\r\n");
return -1;
}
// printf("tmp_offset:%d flv:%p flv_size:%d timestamp:%d\r\n", tmp_offset, flv, flv_size, timestamp);
free(tmp);
uint8_t *flv_next = flv;
while (1) {
if (flv_next >= (flv + flv_size)) break;
struct flv_tag_header_t tag_header = {0};
flv_tag_header_read(&tag_header, flv_next, FLV_TAG_HEAD_LEN);
// printf("flv:%p flv_next:%p tag len:%d timestamp:%d\r\n", flv, flv_next, tag_header.size, timestamp);
rtmp_client_push_video(client, flv + FLV_TAG_HEAD_LEN, tag_header.size, timestamp);
flv_next += FLV_PRE_TAG_LEN + FLV_TAG_HEAD_LEN + tag_header.size;
}
tmp_offset = 0;
}
}
return 0;
}
namespace maix::rtmp {
Rtmp::Rtmp(std::string host, int port, std::string app, std::string stream) {
Rtmp::Rtmp(std::string host, int port, std::string app, std::string stream, int bitrate) {
_host = host;
_port = port;
_app = app;
_stream = stream;
_bitrate = bitrate;
char packet[20 * 1024];
snprintf(packet, sizeof(packet), "rtmp://%s/%s", host.c_str(), app.c_str()); // tcurl
@@ -49,7 +121,7 @@ namespace maix::rtmp {
{
int res = rtmp_client_input(rtmp, packet, r);
if (res != 0) {
throw std::runtime_error("rtmp_client_input failed!");
throw std::runtime_error("rtmp client init failed!");
}
}
@@ -57,18 +129,33 @@ namespace maix::rtmp {
throw std::runtime_error("create lock failed!");
}
if (0 != mmf_init()) {
err::check_raise(err::ERR_RUNTIME, "init mmf failed!");
}
_handler = rtmp;
_thread = nullptr;
_cam = nullptr;
_push_thread = nullptr;
_app_thread = nullptr;
_camera = nullptr;
_start = false;
_capture_image = nullptr;
_need_capture = false;
_path = std::string();
}
Rtmp::~Rtmp() {
if (_start) {
stop();
}
rtmp_client_t* rtmp = (rtmp_client_t *)_handler;
rtmp_client_destroy(rtmp);
socket_close(_socket);
socket_cleanup();
mmf_deinit();
pthread_mutex_destroy(&_lock);
}
@@ -196,6 +283,185 @@ namespace maix::rtmp {
}
}
static void _push_camera_thread(void *args) {
Rtmp *rtmp = (Rtmp *)args;
rtmp->lock(-1);
camera::Camera *camera = rtmp->get_camera();
rtmp_client_t *client = (rtmp_client_t *)rtmp->get_handler();
rtmp->unlock();
uint32_t stream_size = 0;
uint32_t timestamp = 0;
uint64_t curr_ms = time::time_ms();
uint64_t last_ms = curr_ms;
bool first_frame = 1;
mmf_venc_cfg_t cfg = {
.type = 2, //1, h265, 2, h264
.w = camera->width(),
.h = camera->height(),
.fmt = mmf_invert_format_to_mmf(image::Format::FMT_YVU420SP),
.jpg_quality = 0, // unused
.gop = 50,
.intput_fps = 30,
.output_fps = 30,
.bitrate = rtmp->bitrate() / 1000,
};
if (0 != mmf_add_venc_channel(MMF_VENC_CHN, &cfg)) {
err::check_raise(err::ERR_RUNTIME, "mmf venc init failed!");
}
if (0 != maix_avc2flv_init(rtmp->bitrate() / 2)) {
err::check_raise(err::ERR_RUNTIME, "maix avc2flv init failed!");
}
while (1) {
rtmp->lock(-1);
if (!rtmp->is_started()) {
rtmp->unlock();
break;
}
int vi_ch = camera->get_channel();
void *data;
int data_size, width, height, format;
do {
mmf_h265_stream_t stream = {0};
if (mmf_venc_pop(MMF_VENC_CHN, &stream)) {
log::error("mmf_venc_pop failed\n");
mmf_venc_free(MMF_VENC_CHN);
mmf_del_venc_channel(MMF_VENC_CHN);
rtmp->unlock();
break;
}
flv_vec_t vec[8];
for (int i = 0; i < stream.count; i ++) {
vec[i].ptr = stream.data[i];
vec[i].len = stream.data_size[i];
stream_size += stream.data_size[i];
}
if (first_frame) {
last_ms = time::time_ms();
timestamp = 0;
first_frame = false;
}
if (0 != maix_rtmp_client_push_h264(client, vec, stream.count, timestamp)) {
printf("rtmp push failed!\r\n");
}
if (mmf_venc_free(MMF_VENC_CHN)) {
printf("mmf_venc_free failed\n");
mmf_del_venc_channel(MMF_VENC_CHN);
rtmp->unlock();
break;
}
if (mmf_vi_frame_pop(vi_ch, &data, &data_size, &width, &height, &format)) {
log::info("camera read image timeout!\r\n");
rtmp->unlock();
break;
}
curr_ms = time::time_ms();
timestamp = curr_ms - last_ms;
if (data_size > 2560 * 1440 * 3 / 2) {
log::error("image is too large!\r\n");
rtmp->unlock();
break;
}
if (rtmp->get_camera()) {
image::Image *_capture_image = rtmp->get_capture_image();
if (_capture_image && _capture_image->data()) {
delete _capture_image;
rtmp->set_capture_image(NULL);
}
image::Format capture_format = (image::Format)mmf_invert_format_to_maix(format);
bool need_align = (width % mmf_vi_aligned_width(vi_ch) == 0) ? false : true; // Width need align only
switch (capture_format) {
case image::Format::FMT_BGR888: // fall through
case image::Format::FMT_RGB888:
{
_capture_image = rtmp->set_capture_image(new image::Image(width, height, capture_format));
uint8_t * image_data = (uint8_t *)_capture_image->data();
if (need_align) {
for (int h = 0; h < height; h++) {
memcpy((uint8_t *)image_data + h * width * 3, (uint8_t *)data + h * width * 3, width * 3);
}
} else {
memcpy(image_data, data, width * height * 3);
}
}
break;
case image::Format::FMT_YVU420SP:
{
_capture_image = rtmp->set_capture_image(new image::Image(width, height, capture_format));
uint8_t * image_data = (uint8_t *)_capture_image->data();
if (need_align) {
for (int h = 0; h < height * 3 / 2; h ++) {
memcpy((uint8_t *)image_data + h * width, (uint8_t *)data + h * width, width);
}
} else {
memcpy(image_data, data, width * height * 3 / 2);
}
break;
}
default:
{
rtmp->set_capture_image(NULL);
break;
}
}
}
mmf_venc_cfg_t cfg = {0};
if (0 != mmf_venc_get_cfg(MMF_VENC_CHN, &cfg)) {
err::check_raise(err::ERR_RUNTIME, "get venc config failed!\r\n");
}
int img_w = width;
int img_h = height;
int mmf_fmt = format;
if (img_w != cfg.w
|| img_h != cfg.h
|| mmf_fmt != cfg.fmt) {
log::warn("image size or format is incorrect, try to reinit venc!\r\n");
mmf_del_venc_channel(MMF_VENC_CHN);
cfg.w = img_w;
cfg.h = img_h;
cfg.fmt = mmf_invert_format_to_mmf(mmf_fmt);
if (0 != mmf_add_venc_channel(MMF_VENC_CHN, &cfg)) {
err::check_raise(err::ERR_RUNTIME, "mmf venc init failed!\r\n");
}
}
if (mmf_venc_push(MMF_VENC_CHN, (uint8_t *)data, width, height, format)) {
mmf_del_venc_channel(MMF_VENC_CHN);
rtmp->unlock();
err::check_raise(err::ERR_RUNTIME, "mmf venc push failed!\r\n");
break;
}
mmf_vi_frame_free(vi_ch);
} while (stream_size == 0 && rtmp->is_started());
rtmp->unlock();
}
maix_avc2flv_deinit();
if (0 != mmf_del_venc_channel(MMF_VENC_CHN)) {
err::check_raise(err::ERR_RUNTIME, "mmf venc init failed!");
}
}
err::Err Rtmp::lock(uint32_t time) {
uint32_t count = 0;
while (0 != pthread_mutex_trylock(&_lock)) {
@@ -222,12 +488,14 @@ namespace maix::rtmp {
err::Err Rtmp::start(std::string path) {
lock(-1);
if (_start == true) {
unlock();
return err::ERR_BUSY;
}
if (path.size() > 0) {
if (fs::splitext(path) != ".flv") {
log::error("check file path failed!\r\n");
unlock();
return err::ERR_RUNTIME;
}
@@ -236,13 +504,18 @@ namespace maix::rtmp {
_thread = new thread::Thread(_push_file_thread, this);
if (this->_thread == NULL) {
log::error("create camera thread failed!\r\n");
unlock();
return err::ERR_RUNTIME;
}
} else {
if (_cam == nullptr) {
log::error("you need bind camera first!\r\n");
_start = true;
_app_thread = new thread::Thread(_push_camera_thread, this);
if (this->_app_thread == NULL) {
log::error("create camera thread failed!\r\n");
unlock();
return err::ERR_RUNTIME;
}
}
unlock();
@@ -250,12 +523,23 @@ namespace maix::rtmp {
}
err::Err Rtmp::stop() {
lock(-1);
_start = false;
unlock();
_thread->join();
_thread = nullptr;
if (_thread) {
_thread->join();
_thread = nullptr;
}
if (_push_thread) {
_push_thread->join();
_push_thread = nullptr;
}
if (_app_thread) {
_app_thread->join();
_app_thread = nullptr;
}
return err::ERR_NONE;
}
}

View File

@@ -20,7 +20,12 @@ namespace maix::image
if (_format == image::FMT_GRAYSCALE) {
convert_to_imlib_image(this, &src_img);
} else {
gray_img = this->to_format(image::FMT_GRAYSCALE);
if (image::FMT_YVU420SP == _format) {
gray_img = new image::Image(_width, _height, image::FMT_GRAYSCALE);
memcpy(gray_img->data(), _data, _width * _height);
} else {
gray_img = this->to_format(image::FMT_GRAYSCALE);
}
convert_to_imlib_image(gray_img, &src_img);
}

View File

@@ -17,8 +17,9 @@ static int helper(void)
printf( "========================\r\n"
"Intput param:\r\n"
"0 <host> <app> <stream> <file> : rtmp client, push file\r\n"
" example: ./test_media_server 0 192.168.0.30 myapp stream ./test.flv"
"1 <host> <app> <stream> : rtmp client, push camera image\r\n"
" example: ./rtmp_demo 0 192.168.0.30 myapp stream ./test.flv\r\n"
"1 <host> <port> <app> <stream> <bitrate>: rtmp client, push camera image\r\n"
" example: ./rtmp_demo 1 192.168.0.30 1935 live stream 10000000\r\n"
"========================\r\n");
fflush(stdin);
return 0;
@@ -63,6 +64,42 @@ int _main(int argc, char* argv[])
break;
}
case 1:
{
if (argc < 6) {
helper();
break;
}
std::string host = argv[2];
int port = atoi(argv[3]);
std::string app = argv[4];
std::string stream = argv[5];
int bitrate = 1000 * 1000;
if (argc > 6) bitrate = atoi(argv[6]);
printf("push rtmp://%s:%d/%s/%s!\r\n", &host[0], port, &app[0], &stream[0]);
camera::Camera cam = camera::Camera(1280, 720, image::Format::FMT_YVU420SP);
display::Display disp = display::Display();
rtmp::Rtmp rtmp = rtmp::Rtmp(host, port, app, stream, bitrate);
rtmp.bind_camera(&cam);
log::info("start\r\n");
rtmp.start();
while (!app::need_exit()) {
// image::Image *img = rtmp.capture(); // not support now
// if (img) {
// disp.show(*img);
// delete img;
// }
sleep(1);
}
rtmp.stop();
log::info("stop\r\n");
break;
}
default:
{
helper();

View File

@@ -0,0 +1,2 @@
#pragma once