网络摄像头的视频流解析直接使用通过http的Mjpeg是具有边界帧信息的multipart/x-mixed-replace,而jpeg数据只是以二进制形式发送。因此,实际上不需要关心HTTP协议标头。所有jpeg帧均以marker开头,0xff 0xd8并以0xff 0xd9 结尾。因此,代码可以从http流中提取此类帧,解码,编码h264 ,重新发送。
GET /stream/mjpeg HTTP/1.1
Host: 192.168.0.66:2401
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
HTTP/1.1 200 OK
Content-Type: multipart/x-mixed-replace;boundary=----WebKitFormBoundaryIZDrYHwuf2VJdpHw
Cache-Control: no-cache
------WebKitFormBoundaryIZDrYHwuf2VJdpHw
Content-Type: image/jpg
Content-Length: 61108
…JFIF…
以下使用python来读http mjpeg,因为python 方便快速,可以做原型方法的测试
import cv2
import requests
import numpy as np
r = requests.get('http://192.168.1.xx/mjpeg.cgi', auth=('user', 'password'), stream=True)
if(r.status_code == 200):
bytes = bytes()
for chunk in r.iter_content(chunk_size=1024):
bytes += chunk
a = bytes.find(b'\xff\xd8')
b = bytes.find(b'\xff\xd9')
if a != -1 and b != -1:
jpg = bytes[a:b+2]
bytes = bytes[b+2:]
i = cv2.imdecode(np.fromstring(jpg, dtype=np.uint8), cv2.IMREAD_COLOR)
cv2.imshow('i', i)
if cv2.waitKey(1) == 27:
exit(0)
else:
print("Received unexpected status code {}".format(r.status_code))
java读取也是很方便的
package net.thistleshrub.mechwarfare.mjpeg;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.URLConnection;
import javax.imageio.ImageIO;
public class MjpegRunner implements Runnable
{
private static final String CONTENT_LENGTH = "Content-length: ";
private static final String CONTENT_TYPE = "Content-type: image/jpeg";
private MJpegViewer viewer;
private InputStream urlStream;
private StringWriter stringWriter;
private boolean processing = true;
public MjpegRunner(MJpegViewer viewer, URL url) throws IOException
{
this.viewer = viewer;
URLConnection urlConn = url.openConnection();
// change the timeout to taste, I like 1 second
urlConn.setReadTimeout(1000);
urlConn.connect();
urlStream = urlConn.getInputStream();
stringWriter = new StringWriter(128);
}
public synchronized void stop()
{
processing = false;
}
@Override
public void run()
{
while(processing)
{
try
{
byte[] imageBytes = retrieveNextImage();
ByteArrayInputStream bais = new ByteArrayInputStream(imageBytes);
BufferedImage image = ImageIO.read(bais);
viewer.setBufferedImage(image);
viewer.repaint();
}catch(SocketTimeoutException ste){
System.err.println("failed stream read: " + ste);
viewer.setFailedString("Lost Camera connection: " + ste);
viewer.repaint();
stop();
}catch(IOException e){
System.err.println("failed stream read: " + e);
stop();
}
}
// close streams
try
{
urlStream.close();
}catch(IOException ioe){
System.err.println("Failed to close the stream: " + ioe);
}
}
/**
* Using the urlStream get the next JPEG image as a byte[]
* @return byte[] of the JPEG
* @throws IOException
*/
private byte[] retrieveNextImage() throws IOException
{
boolean haveHeader = false;
int currByte = -1;
String header = null;
// build headers
// the DCS-930L stops it's headers
while((currByte = urlStream.read()) > -1 && !haveHeader)
{
stringWriter.write(currByte);
String tempString = stringWriter.toString();
int indexOf = tempString.indexOf(CONTENT_TYPE);
if(indexOf > 0)
{
haveHeader = true;
header = tempString;
}
}
// 255 indicates the start of the jpeg image
while((urlStream.read()) != 255)
{
// just skip extras
}
// rest is the buffer
int contentLength = contentLength(header);
byte[] imageBytes = new byte[contentLength + 1];
// since we ate the original 255 , shove it back in
imageBytes[0] = (byte)255;
int offset = 1;
int numRead = 0;
while (offset < imageBytes.length
&& (numRead=urlStream.read(imageBytes, offset, imageBytes.length-offset)) >= 0)
{
offset += numRead;
}
stringWriter = new StringWriter(128);
return imageBytes;
}
// dirty but it works content-length parsing
private static int contentLength(String header)
{
int indexOfContentLength = header.indexOf(CONTENT_LENGTH);
int valueStartPos = indexOfContentLength + CONTENT_LENGTH.length();
int indexOfEOL = header.indexOf('\n', indexOfContentLength);
String lengthValStr = header.substring(valueStartPos, indexOfEOL).trim();
int retValue = Integer.parseInt(lengthValStr);
return retValue;
}
}
以下这段代码我在其他文章里面写过,现在把他加上,省的其他人找了
void capture_start(s_param *param)
{
//开始视频采集
if (param != NULL)
{
asio::io_context io_service;
tcp::resolver resolver(io_service);
tcp::resolver::query query(param->host, param->port);
tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
tcp::resolver::iterator end;
tcp::socket socket(io_service);
//socket.io_control(asio::ip::tcp::socket::non_blocking(true));
std::error_code error = asio::error::host_not_found;
while (error && endpoint_iterator != end) {
socket.close();
//int timeout = 3000;
//int nRet = setsockopt(socket.native_handle(), SOL_SOCKET, SO_CONNECT_TIME, (const char*)&timeout, sizeof(timeout));
socket.connect(*endpoint_iterator++, error);
}
if (error) {
cout << "can not connect the camera!" << endl;
return;
//throw asio::system_error(error);
}
// Form the request. We specify the "Connection: close" header so that the
// server will close the socket after transmitting the response. This will
// allow us to treat all data up until the EOF as the content.
asio::streambuf request;
ostream request_stream(&request);
request_stream << "GET " <<param->route << " HTTP/1.0\r\n";
request_stream << "Host: " << param->host << "\r\n";
request_stream << "Accept: */*\r\n";
request_stream << "Connection: close\r\n\r\n";
// Send the request.
asio::write(socket, request);
// Read the response status line. The response streambuf will automatically
// grow to accommodate the entire line. The growth may be limited by passing
// a maximum size to the streambuf constructor.
asio::streambuf response;
asio::read_until(socket, response, "\r\n");
// Check that response is OK.
istream response_stream(&response);
string http_version;
response_stream >> http_version;
unsigned int status_code;
response_stream >> status_code;
string status_message;
getline(response_stream, status_message);
if (!response_stream || http_version.substr(0, 5) != "HTTP/") {
cout << "Invalid response\n";
return;
}
if (status_code != 200) {
cout << "Response returned with status code " << status_code << "\n";
return;
}
// Read the response headers, which are terminated by a blank line.
asio::read_until(socket, response, "\r\n\r\n");
// Get the MIME multipart boundary from the headers.
regex rx_content_type("Content-Type:.*boundary=(.*)");
regex rx_content_length("Content-Length: (.*)");
smatch match;
string header;
string boundary;
while (getline(response_stream, header) && header != "\r") {
//cout << "HTTP HEADER: " << header << endl;
if (regex_search(header, match, rx_content_type)) {
boundary = match[1];
//cout << "BOUNDARY SELECTED: " << boundary << endl;
}
}
// Abort if a boundary was not found.
if (boundary == "") {
cout << "Not a MJPEG stream" << endl;
return;
}
std::vector<uint8_t> buff;
while (1) {
asio::read_until(socket, response, boundary);
while (getline(response_stream, header)) {
if (header.find(boundary + "\r") != string::npos)
{
break;
}
}
uint32_t content_length;
while (getline(response_stream, header) && header != "\r") {
if (regex_search(header, match, rx_content_length)) {
std::ssub_match base_sub_match = match[1];
content_length = std::atoi(base_sub_match.str().c_str());
}
}
if (response.size() < content_length) {
asio::read(socket, response, asio::transfer_at_least(
content_length - response.size()));
}
if (buff.size() < content_length) {
buff.resize(content_length);
}
response.sgetn((char*)&buff[0], content_length);
cv::Mat mat(buff);
cv::Mat dec = imdecode(Mat(buff), IMREAD_COLOR);
v_mut_src.lock();
cv::resize(dec, v_srcImg, cv::Size(CANVAS_WIDTH, CANVAS_HEIGHT));
v_mut_src.unlock();
//cout << "RESPONSE SIZE, BEFORE JPEG CONSUME: " << response.size() << endl;
cv::imshow("windows", v_srcImg);
cv::waitKey(2);
response.consume(content_length);
}
}
std::cout << "capture break...." << std::endl;
}