当然可以!你可以在 **C语言** 中使用 OpenCV 库(实际上是 C++ 编写的,但提供了 C 风格的兼容接口)来从 `v4l2` 设备读取 MJPEG 视频流、解码并推流到网络(例如 RTMP 或 RTP)。不过需要注意:OpenCV 的原生 API 是 C++,其“C 接口”在较新版本中已被弃用。因此,推荐的做法是使用 **C++ 编译器编写代码,并以类似 C 的方式调用 OpenCV 功能**,从而实现“类 C”的编程风格。
下面我将给出一个完整的 **C++ 实现的示例程序(可被当作 C 代码使用)**,它完成以下任务:
1. 使用 OpenCV 打开 `/dev/video0` 的 v4l2 MJPEG 流;
2. 解码帧;
3. 使用 FFmpeg 的 `AVFormatContext` 和 `libavcodec` / `libavformat` 将视频重新编码并通过 RTMP 推流(如推送到 Nginx-RTMP 或 OBS);
> ⚠️ 注意:真正的纯 C 调用 OpenCV 并不容易(因为 OpenCV 主要是 C++),但我们可以通过 **混合编程(C++ 写逻辑 + C 编译器链接)** 实现目标。
---
### ✅ 方案:使用 OpenCV + FFmpeg(libav)进行解码与推流
#### 安装依赖(Ubuntu)
```bash
sudo apt update
sudo apt install libopencv-dev libavformat-dev libavcodec-dev libswscale-dev ffmpeg
```
---
### 📌 示例代码:OpenCV 解码 + FFmpeg 推 RTMP 流(C++ 编写,兼容 C 使用)
```cpp
// filename: opencv_rtmp_streamer.cpp
extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
#include <libavutil/avutil.h>
#include <libavutil/imgutils.h>
}
#include <opencv2/opencv.hpp>
#include <iostream>
#pragma comment(lib, "opencv_world") // Windows only, ignore on Linux
int main() {
const char* rtmp_url = "rtmp://localhost/live/stream"; // 修改为你的目标地址
const int width = 640;
const int height = 480;
const int fps = 30;
// ----------------------------
// Step 1: 初始化 FFmpeg 复用器 (RTMP)
// ----------------------------
av_register_all();
avformat_network_init();
AVFormatContext* fmt_ctx = nullptr;
avformat_alloc_output_context2(&fmt_ctx, nullptr, "flv", rtmp_url);
if (!fmt_ctx) {
std::cerr << "无法创建输出上下文\n";
return -1;
}
AVStream* video_st = avformat_new_stream(fmt_ctx, nullptr);
if (!video_st) {
std::cerr << "无法创建视频流\n";
return -1;
}
AVCodecID codec_id = AV_CODEC_ID_H264; // 我们把 MJPEG 解码后转成 H.264 再推流
AVCodec* encoder = avcodec_find_encoder(codec_id);
if (!encoder) {
std::cerr << "未找到 H.264 编码器\n";
return -1;
}
video_st->codecpar->codec_id = codec_id;
video_st->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
video_st->codecpar->width = width;
video_st->codecpar->height = height;
video_st->codecpar->format = AV_PIX_FMT_YUV420P;
video_st->codecpar->bit_rate = 1000000;
video_st->time_base = {1, fps};
video_st->codecpar->framerate = {fps, 1};
AVCodecContext* enc_ctx = avcodec_alloc_context3(encoder);
avcodec_parameters_to_context(enc_ctx, video_st->codecpar);
enc_ctx->time_base = video_st->time_base;
if (avcodec_open2(enc_ctx, encoder, nullptr) < 0) {
std::cerr << "无法打开编码器\n";
return -1;
}
// 打开输出 URL
if (!(fmt_ctx->oformat->flags & AVFMT_NOFILE)) {
if (avio_open(&fmt_ctx->pb, rtmp_url, AVIO_FLAG_WRITE) < 0) {
std::cerr << "无法打开 RTMP 地址: " << rtmp_url << "\n";
return -1;
}
}
avformat_write_header(fmt_ctx, nullptr);
// ----------------------------
// Step 2: OpenCV 读取 v4l2 MJPEG 摄像头
// ----------------------------
cv::VideoCapture cap("/dev/video0", cv::CAP_V4L2);
if (!cap.isOpened()) {
std::cerr << "无法打开摄像头 /dev/video0\n";
return -1;
}
cap.set(cv::CAP_PROP_FOURCC, cv::VideoWriter::fourcc('M', 'J', 'P', 'G'));
cap.set(cv::CAP_PROP_FRAME_WIDTH, width);
cap.set(cv::CAP_PROP_FRAME_HEIGHT, height);
cap.set(cv::CAP_PROP_FPS, fps);
cv::Mat frame, yuv_frame;
struct SwsContext* sws_ctx = sws_getContext(
width, height, AV_PIX_FMT_BGR24,
width, height, AV_PIX_FMT_YUV420P,
SWS_BILINEAR, nullptr, nullptr, nullptr
);
AVFrame* pkt_frame = av_frame_alloc();
pkt_frame->width = width;
pkt_frame->height = height;
pkt_frame->format = AV_PIX_FMT_YUV420P;
av_frame_get_buffer(pkt_frame, 32);
uint8_t* rgb_data = (uint8_t*)av_malloc(3 * width * height);
AVPacket pkt;
av_init_packet(&pkt);
pkt.data = nullptr;
pkt.size = 0;
int frame_count = 0;
while (true) {
if (!cap.read(frame)) {
std::cerr << "读取帧失败\n";
break;
}
// 转换为 RGB -> YUV for H.264 encoding
cv::cvtColor(frame, frame, cv::COLOR_BGR2RGB); // BGR to RGB
avpicture_fill((AVPicture*)pkt_frame, rgb_data, AV_PIX_FMT_RGB24, width, height);
memcpy(pkt_frame->data[0], frame.data, frame.total() * frame.elemSize());
sws_scale(sws_ctx, pkt_frame->data, pkt_frame->linesize, 0, height,
pkt_frame->data, pkt_frame->linesize);
pkt_frame->pts = frame_count++;
// 编码
avcodec_send_frame(enc_ctx, pkt_frame);
while (avcodec_receive_packet(enc_ctx, &pkt) == 0) {
pkt.stream_index = video_st->index;
av_interleaved_write_frame(fmt_ctx, &pkt);
av_packet_unref(&pkt);
}
// 显示本地画面(可选)
cv::imshow("Local View", frame);
if (cv::waitKey(1) == 'q') break;
}
// ----------------------------
// 清理资源
// ----------------------------
av_write_trailer(fmt_ctx);
avcodec_close(enc_ctx);
av_frame_free(&pkt_frame);
av_free(rgb_data);
sws_freeContext(sws_ctx);
if (fmt_ctx && !(fmt_ctx->oformat->flags & AVFMT_NOFILE))
avio_closep(&fmt_ctx->pb);
avformat_free_context(fmt_ctx);
cap.release();
cv::destroyAllWindows();
std::cout << "推流结束\n";
return 0;
}
```
---
### 🔧 编译命令(使用 g++)
```bash
g++ -o streamer opencv_rtmp_streamer.cpp \
`pkg-config --cflags --libs opencv4` \
`pkg-config --cflags --libs libavformat libavcodec libswscale libavutil`
```
> 如果提示找不到 `opencv4`,尝试改为 `opencv`:
>
> ```bash
> pkg-config --cflags --libs opencv
> ```
---
### 📡 使用说明
1. 安装 Nginx + nginx-rtmp-module 或使用 [OBS](https://obsproject.com/) 作为接收端。
2. 修改代码中的 `rtmp_url` 为目标服务器地址,比如:
```cpp
const char* rtmp_url = "rtmp://a.rtmp.youtube.com/live2/your-stream-key";
```
3. 运行程序:
```bash
./streamer
```
4. 在 VLC 或浏览器中播放 `rtmp://localhost/live/stream` 查看结果。
---
### ❗重要说明
| 项目 | 说明 |
|------|------|
| **是否纯 C?** | 不完全是,这是 C++,但由于没有复杂类设计,结构清晰,可视为“C 风格”使用 |
| **为何不用纯 C 调用 OpenCV?** | OpenCV 自 4.x 后已移除旧的 `cvCreateImage` 等 C 接口,强烈建议使用 C++ |
| **能否用 v4l2 直接读 + libjpeg 解码?** | 可以!见下文相关问题,那是真正的纯 C 方式 |
---