之前做了linux x86_64上的摄像头采集、屏幕采集和麦克风等采集,并把采集到的音视频数据实时编码输出为RTMP/RTSP流, 现在国产arm64位设备越来越多,最近对linux arm64也做了相应的支持.
Linux上摄像头采集使用V4L2相关接口,查看摄像头设备文件可以使用(ls -l /dev/|grep video). 打开设备使用open接口就行, 例如:open("/dev/videoxx", flag).
屏幕采集用X相关接口实现,如果是Wayland协议, 用PipeWire相关接口实现采集就好.
麦克风采集使用ALSA或者PulseAudio.
采集播放音频用PulseAudio.
采集实现代码很多, 已封装好接口,下面是调用demo:
/*
* 问题沟通微信:ldxevt
*/
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <assert.h>
#include <string.h>
#include <poll.h>
#include <errno.h>
#include <string>
#include <vector>
#include <memory>
#include <sstream>
#include <X11/Xlib.h>
#include <X11/keysym.h>
#include "nt_sdk_linux_smart_log.h"
#include "nt_common_media_define.h"
#include "nt_linux_smart_publisher_sdk.h"
#include "nt_layer_conf_wrapper.h"
namespace
{
int EventPoll(int fd, bool is_write, int timeout_ms)
{
int result;
do
{
struct pollfd info;
info.fd = fd;
if (is_write)
{
info.events = POLLOUT;
}
else
{
info.events = POLLIN | POLLPRI;
}
result = poll(&info, 1, timeout_ms);
} while (result < 0 && errno == EINTR);
return result;
}
bool MY_X11_Pending(Display* display, int timeout_ms)
{
XFlush(display);
if (XEventsQueued(display, QueuedAlready) > 0)
{
return true;
}
if (EventPoll(ConnectionNumber(display), false, timeout_ms))
{
if (XPending(display) > 0)
{
return true;
}
}
return false;
}
Window CreateSubWindow(Display* display, int screen, Window parent)
{
XWindowAttributes parent_win_att;
XGetWindowAttributes(display, parent, &parent_win_att);
fprintf(stdout, "parent w:%d, h:%d\n", parent_win_att.width, parent_win_att.height);
XSetWindowAttributes swa;
swa.border_pixel = WhitePixel(display, screen);
swa.event_mask = KeyPressMask | StructureNotifyMask;
return XCreateWindow(display, parent, 0, 0, parent_win_att.width - 4, parent_win_att.height - 4,
2, parent_win_att.depth, InputOutput, parent_win_att.visual, CWEventMask | CWBorderPixel, &swa);
}
class CameraInfo
{
public:
std::string name_;
std::string id_;
std::vector<NT_PB_VideoCaptureCapability> capabilities_;
};
volatile bool g_is_exit = false;
void OnSaSigaction(int signo, siginfo_t* s_info, void*)
{
if (SIGINT == signo)
{
g_is_exit = true;
fprintf(stdout, "OnSaSigaction SIGINT si_pid=%d, pid=%d\n", (int)s_info->si_pid, (int)getpid());
}
else if (SIGFPE == signo)
{
fprintf(stderr, "OnSaSigaction SIGFPE si_pid=%d, pid=%d, addr=%p\n", (int)s_info->si_pid, (int)getpid(), s_info->si_addr);
switch (s_info->si_code)
{
case FPE_INTDIV:
fprintf(stderr, "OnSaSigaction SIGFPE FPE_INTDIV\n");
break;
case FPE_INTOVF:
fprintf(stderr, "OnSaSigaction SIGFPE FPE_INTOVF\n");
break;
case FPE_FLTDIV:
fprintf(stderr, "OnSaSigaction SIGFPE FPE_FLTDIV\n");
break;
case FPE_FLTOVF:
fprintf(stderr, "OnSaSigaction SIGFPE FPE_FLTOVF\n");
break;
case FPE_FLTUND:
fprintf(stderr, "OnSaSigaction SIGFPE FPE_FLTUND\n");
break;
case FPE_FLTRES:
fprintf(stderr, "OnSaSigaction SIGFPE FPE_FLTRES\n");
break;
case FPE_FLTINV:
fprintf(stderr, "OnSaSigaction SIGFPE FPE_FLTINV\n");
break;
case FPE_FLTSUB:
fprintf(stderr, "OnSaSigaction SIGFPE FPE_FLTSUB\n");
break;
default:
break;
}
}
}
void OnSDKEventHandle(NT_HANDLE handle, NT_PVOID user_data,
NT_UINT32 event_id,
NT_INT64 param1,
NT_INT64 param2,
NT_UINT64 param3,
NT_UINT64 param4,
NT_PCSTR param5,
NT_PCSTR param6,
NT_PVOID param7
)
{
if (NT_PB_E_EVENT_ID_CAPTURE_WINDOW_INVALID == event_id)
{
fprintf(stdout, "X Window is invalid, wid=%lld, handle=%p\n", param1, handle);
}
}
void LogInit()
{
SmartLogAPI log_api;
memset(&log_api, 0, sizeof(log_api));
GetSmartLogAPI(&log_api);
log_api.SetLevel(SL_INFO_LEVEL);
log_api.SetPath((NT_PVOID)"./");
}
bool PushSDKInit(NT_SmartPublisherSDKAPI& push_api)
{
memset(&push_api, 0, sizeof(push_api));
NT_GetSmartPublisherSDKAPI(&push_api);
auto ret = push_api.Init(0, nullptr);
if (NT_ERC_OK != ret)
{
fprintf(stderr, "push_api.Init failed!\n");
return false;
}
else
{
fprintf(stdout, "push_api.Init ok!\n");
}
return true;
}
std::vector<NT_UINT64> x_win_list;
void GetCameraInfo(NT_SmartPublisherSDKAPI* push_api, std::vector<CameraInfo>& cameras)
{
NT_INT32 device_number = 0;
if (NT_ERC_OK != push_api->GetVideoCaptureDeviceNumber(&device_number))
{
return;
}
if (device_number < 1)
{
return;
}
for (auto i = 0; i < device_number; ++i)
{
CameraInfo info;
char name[256] = { 0 };
char id[1024] = { 0 };
if (NT_ERC_OK != push_api->GetVideoCaptureDeviceInfo(i,
name, sizeof(name) / sizeof(char),
id, sizeof(id) / sizeof(char)))
{
continue;
}
info.name_ = name;
info.id_ = id;
NT_INT32 capability_number = 0;
if (NT_ERC_OK != push_api->GetVideoCaptureDeviceCapabilityNumber(
id, &capability_number))
{
continue;
}
auto is_failed = false;
for (auto i = 0; i < capability_number; ++i)
{
NT_PB_VideoCaptureCapability capability = { 0, 0, 0 };
if (NT_ERC_OK != push_api->GetVideoCaptureDeviceCapability(
id, i, &capability))
{
is_failed = true;
break;
}
info.capabilities_.push_back(capability);
}
if (!is_failed)
{
cameras.push_back(info);
}
}
}
NT_HANDLE StartPush(NT_SmartPublisherSDKAPI* push_api, const std::string& rtmp_url, int dst_fps)
{
NT_INT32 pulse_device_number = 0;
if (NT_ERC_OK == push_api->GetAuidoInputDeviceNumber(2, &pulse_device_number))
{
fprintf(stdout, "Pulse device num:%d\n", pulse_device_number);
char device_name[512];
for (auto i = 0; i < pulse_device_number; ++i)
{
if (NT_ERC_OK == push_api->GetAuidoInputDeviceName(2, i, device_name, 512))
{
fprintf(stdout, "index:%d name:%s\n", i, device_name);
}
}
}
NT_INT32 alsa_device_number = 0;
if (pulse_device_number < 1)
{
if (NT_ERC_OK == push_api->GetAuidoInputDeviceNumber(1, &alsa_device_number))
{
fprintf(stdout, "Alsa device num:%d\n", alsa_device_number);
char device_name[512];
for (auto i = 0; i < alsa_device_number; ++i)
{
if (NT_ERC_OK == push_api->GetAuidoInputDeviceName(1, i, device_name, 512))
{
fprintf(stdout, "index:%d name:%s\n", i, device_name);
}
}
}
}
NT_INT32 capture_speaker_flag = 0;
if ( NT_ERC_OK == push_api->IsCanCaptureSpeaker(2, &capture_speaker_flag) )
{
if (capture_speaker_flag)
fprintf(stdout, "Support speaker capture\n");
else
fprintf(stdout, "UnSupport speaker capture\n");
}
NT_INT32 is_support_window_capture = 0;
if (NT_ERC_OK == push_api->IsCaptureXWindowSupported(NULL, &is_support_window_capture))
{
if (is_support_window_capture)
fprintf(stdout, "Support window capture\n");
else
fprintf(stdout, "UnSupport window capture\n");
}
if (is_support_window_capture)
{
NT_INT32 win_count = 0;
if (NT_ERC_OK == push_api->UpdateCaptureXWindowList(NULL, &win_count) && win_count > 0 )
{
fprintf(stdout, "X Capture Winows list++\n");
for (auto i = 0; i < win_count; ++i)
{
NT_UINT64 wid;
char title[512];
if (NT_ERC_OK == push_api->GetCaptureXWindowInfo(i, &wid, title, sizeof(title) / sizeof(char)))
{
x_win_list.push_back(wid);
fprintf(stdout, "wid:%llu, title:%s\n", wid, title);
}
}
fprintf(stdout, "X Capture Winows list--\n");
}
}
std::vector<CameraInfo> cameras;
GetCameraInfo(push_api, cameras);
if (!cameras.empty())
{
fprintf(stdout, "cameras count:%d\n", (int)cameras.size());
for (const auto& c : cameras)
{
fprintf(stdout, "camera name:%s, id:%s, cap_num:%d\n", c.name_.c_str(), c.id_.c_str(), (int)c.capabilities_.size());
for (const auto& i : c.capabilities_)
{
fprintf(stdout, "cap w:%d, h:%d, fps:%d\n", i.width_, i.height_, i.max_frame_rate_);
}
}
}
NT_UINT32 auido_option = NT_PB_E_AUDIO_OPTION_NO_AUDIO;
if (pulse_device_number > 0 || alsa_device_number > 0)
{
auido_option = NT_PB_E_AUDIO_OPTION_CAPTURE_MIC;
}
else if (capture_speaker_flag)
{
auido_option = NT_PB_E_AUDIO_OPTION_CAPTURE_SPEAKER;
}
//auido_option = NT_PB_E_AUDIO_OPTION_CAPTURE_MIC_SPEAKER_MIXER;
NT_UINT32 video_option = NT_PB_E_VIDEO_OPTION_SCREEN;
if (!cameras.empty())
{
video_option = NT_PB_E_VIDEO_OPTION_CAMERA;
}
else if (is_support_window_capture)
{
video_option = NT_PB_E_VIDEO_OPTION_WINDOW;
}
NT_HANDLE push_handle = nullptr;
if (NT_ERC_OK != push_api->Open(&push_handle, video_option, auido_option, 0, NULL))
{
return nullptr;
}
push_api->SetEventCallBack(push_handle, nullptr, OnSDKEventHandle);
if (video_option == NT_PB_E_VIDEO_OPTION_CAMERA)
{
if (!cameras.empty())
{
push_api->SetVideoCaptureDeviceBaseParameter(push_handle, cameras.front().id_.c_str(),
640, 480);
//push_api->FlipVerticalCamera(push_handle, 1);
//push_api->FlipHorizontalCamera(push_handle, 1);
//push_api->RotateCamera(push_handle, 0);
}
}
if (video_option == NT_PB_E_VIDEO_OPTION_WINDOW)
{
if (!x_win_list.empty())
{
//push_api->SetCaptureXWindow(push_handle, x_win_list[0]);
push_api->SetCaptureXWindow(push_handle, x_win_list.back());
}
}
push_api->SetFrameRate(push_handle, dst_fps);
push_api->SetVideoEncoder(push_handle, 0, 1, NT_MEDIA_CODEC_ID_H264, 0);
push_api->SetVideoBitRate(push_handle, 2000);
push_api->SetVideoQuality(push_handle, 26);
push_api->SetVideoMaxBitRate(push_handle, 4000);
push_api->SetVideoKeyFrameInterval(push_handle, dst_fps*2);
push_api->SetVideoEncoderProfile(push_handle, 3); // H264 high
push_api->SetVideoEncoderSpeed(push_handle, 3);
if (pulse_device_number > 0)
{
push_api->SetAudioInputLayer(push_handle, 2);
push_api->SetAuidoInputDeviceId(push_handle, 0);
}
else if (alsa_device_number > 0)
{
push_api->SetAudioInputLayer(push_handle, 1);
push_api->SetAuidoInputDeviceId(push_handle, 0);
}
push_api->SetPublisherAudioCodecType(push_handle, 1);
//push_api->SetMute(push_handle, 1);
if ( NT_ERC_OK != push_api->SetURL(push_handle, rtmp_url.c_str(), NULL) )
{
push_api->Close(push_handle);
push_handle = nullptr;
return nullptr;
}
if ( NT_ERC_OK != push_api->StartPublisher(push_handle, NULL) )
{
push_api->Close(push_handle);
push_handle = nullptr;
return nullptr;
}
return push_handle;
}
}
int main(int argc, char *argv[])
{
struct sigaction act;
sigemptyset(&act.sa_mask);
act.sa_sigaction = OnSaSigaction;
act.sa_flags = SA_SIGINFO;
sigaction(SIGINT, &act, NULL);
sigaction(SIGFPE, &act, NULL);
XInitThreads(); // X支持多线程, 必须调用
auto display = XOpenDisplay(nullptr);
if (!display)
{
fprintf(stderr, "Cannot connect to X server\n");
return 0;
}
auto screen = DefaultScreen(display);
auto root = XRootWindow(display, screen);
XWindowAttributes root_win_att;
if (!XGetWindowAttributes(display, root, &root_win_att))
{
fprintf(stderr, "Get Root window attri failed\n");
XCloseDisplay(display);
return 0;
}
int main_w = root_win_att.width / 2, main_h = root_win_att.height / 2;
auto black_pixel = BlackPixel(display, screen);
auto white_pixel = WhitePixel(display, screen);
auto main_wid = XCreateSimpleWindow(display, root, 0, 0, main_w, main_h, 0, white_pixel, black_pixel);
if (!main_wid)
{
fprintf(stderr, "Cannot Create Main Window\n");
XCloseDisplay(display);
return 0;
}
XSelectInput(display, main_wid, StructureNotifyMask | KeyPressMask);
auto sub_wid = CreateSubWindow(display, screen, main_wid);
if (!sub_wid)
{
fprintf(stderr, "Cannot Create Render Window\n");
XDestroyWindow(display, main_wid);
XCloseDisplay(display);
return 0;
}
XMapWindow(display, main_wid);
XStoreName(display, main_wid, "Video Preview");
XMapWindow(display, sub_wid);
LogInit();
NT_SmartPublisherSDKAPI push_api;
if (!PushSDKInit(push_api))
{
XDestroyWindow(display, sub_wid);
XDestroyWindow(display, main_wid);
XCloseDisplay(display);
return 0;
}
auto push_handle = StartPush(&push_api, "rtmp://192.168.0.131:1935/live/test", 25);
if (!push_handle)
{
fprintf(stderr, "start push failed.\n");
XDestroyWindow(display, sub_wid);
XDestroyWindow(display, main_wid);
XCloseDisplay(display);
push_api.UnInit();
return 0;
}
// 开启预览,也可以不开启, 根据需求来
push_api.SetPreviewXWindow(push_handle, "", sub_wid);
push_api.StartPreview(push_handle, 0, nullptr);
while (!g_is_exit)
{
while (MY_X11_Pending(display, 10))
{
XEvent xev;
memset(&xev, 0, sizeof(xev));
XNextEvent(display, &xev);
if (xev.type == ConfigureNotify)
{
if (xev.xconfigure.window == main_wid)
{
if (xev.xconfigure.width != main_w || xev.xconfigure.height != main_h)
{
main_w = xev.xconfigure.width;
main_h = xev.xconfigure.height;
XMoveResizeWindow(display, sub_wid, 0, 0, main_w - 4, main_h - 4);
}
}
}
else if (xev.type == KeyPress)
{
if (xev.xkey.keycode == XKeysymToKeycode(display, XK_Escape))
{
fprintf(stdout, "ESC Key Press\n");
g_is_exit = true;
}
}
if (g_is_exit)
break;
}
}
fprintf(stdout, "exit run loop, is_exit:%d\n", g_is_exit);
push_api.StopPreview(push_handle);
push_api.StopPublisher(push_handle);
push_api.Close(push_handle);
push_handle = nullptr;
XDestroyWindow(display, sub_wid);
XDestroyWindow(display, main_wid);
XCloseDisplay(display);
push_api.UnInit();
fprintf(stdout, "SDK UnInit..\n");
return 0;
}
上面的代码音视频采集都有,视频采集有屏幕、窗口和摄像头, 音频采集麦克风、扬声器(两者混音也支持). 然后使用rtmp协议推送出去, 这里没输出rtsp流的代码,需要的话沟通就好, 不依赖QT, 但可以集成到QT项目将中, Linux arm64和x86_64都支持。