微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

NodeJS无法正确还原Base64?

如何解决NodeJS无法正确还原Base64?

我想使用PiCamera在类似于安全摄像机的设置中流式传输视频,并首先使用MJPEG。我设法从RaspBerry Pi网站获得了一个演示服务器,可以正常工作。我使用了<img>标签,背景图片,更多的HTTP标头,更少的HTTP标头来使其工作多种方式。但是,我不想使用Python作为服务器,而是想使用node。

我找不到不使用Python的PiCamera的等效节点。因此,我决定将每个图像转换为Base64并将其发布到stdout,然后使用child_process接受节点中的输出。以后我可能会考虑类似IPC或ShellJS的东西。似乎没有办法将图像作为Base64发送。 Content-Type不接受“ base64”的字符集,并且HTTP不支持Content-transfer-encoding。因此,需要将图像转换回二进制文件,以便沿着电线发送。实际响应应该与Python演示服务器几乎相同。

我可以将图像从Python发送到节点,它们看起来与Python演示服务器中的图像相同。很难将图像相互比较,但是前几个字节和最后几个字节看起来是相同的。但是,该流不会显示在节点服务器的HTML中。它是白色的,当我将MJPEG URL直接放到浏览器中时,整个页面都是黑色的,出于某种原因,它的中心常常只有“ ht”。我已经检查了两个服务器上的标头,找不到任何错误。我注意到节点认被硬编码为HTTP 1.1,而Python认使用HTTP 1.0,并且我读到1.1要求更改ConnectionKeep-Alive标头,但是我将Python服务器更改为HTTP 1.1,并且一切继续进行。

两台服务器上的图像内容都与我训练有素的眼睛足够相似。我无法复制二进制文件,但是反转后的二进制文件与原始base64相同。将单个base64图像复制到数据URL中会生成有效图像。那么还有什么可能呢?

节点服务器:

const frameStart = '/9j/';

const {url} = import.Meta;
const {pathname: __filename} = new URL(url);

import {spawn} from 'child_process';
import {Passthrough} from 'stream';
import {extname} from 'path';
import {createServer} from 'http';
import Koa from 'koa';
import router from 'koa-route';
import chalk from 'chalk';

const {get: getRoute,post: postRoute} = router;
const {green,gray,magenta,cyan} = chalk;

const server = new Koa();

const html = '<title>picamera MJPEG streaming demo</title>' +
'<style>' +
'.live-image {' +
    'background-image: url(/live.mjpeg);' +
    'width: 640px;' +
    'height: 480px;' +
'}' +
'</style>' +

'<h1>PiCamera MJPEG Streaming Demo</h1>' +
'<div class="live-image"></div>';

server.liveStream = new Passthrough();

server.use(({response: res},next) => {
  res.set('Server','');
  next();
})
  .use(getRoute('/',({response: res}) => {
    Object.assign(res,{status: 200,body: html});
  }))
  .use(postRoute('/start-live',({app,response: res}) => {
    res.status = 200;
    startLive(app);
  }))
  .use(({app,method,path,response: res},next) => {
    if(method !== 'GET' || extname(path) !== '.mjpeg') {
      return next();
    }

    res.body = new Passthrough();

    startLive(app);

    res.status = 200;
    res.type = 'multipart/x-mixed-replace; boundary=FRAME';

    res.set('Cache-Control','no-store');
    res.remove('Keep-Alive');
    res.remove('Connection');
    res.remove('transfer-encoding');

    const typeHeader = 'Content-Type: image/jpeg';

    app.liveStream.on('data',bufData64 => {
      const data64 = bufData64.toString();
      const binData = Buffer.from(data64,'base64').toString('latin1');
      const lengthHeader = `Content-Length: ${binData.length}`;
      res.body.push(`--FRAME\n${typeHeader}\n${lengthHeader}\n\n${binData}\n`);
    });
  });

export default server;

if(process.argv[1] === __filename) {
  listen();
}

export async function listen() {
  const app = createServer({},server.callback());

  app.listen(3000);

  console.log(green(cyan('BFeeder'),'serving on port',gray(3000)));
}

function startLive(server) {
  const {liveStream} = server;

  return new Promise(resolve => {
    let frame = '';

    const {stdout} = spawn('./photo_rpi.py');
    stdout.on('data',out => {
      out = out.toString();
      const [first,...pieces] = out.split(frameStart);
      const last = pieces.pop();

      frame += first;
      if(frame && (pieces.length || last)) {
        liveStream.push(frame);
        frame = '';
      }

      for(const [index,piece] of Object.entries(pieces)) {
        frame += `${frameStart}${piece}`;
        if(index < pieces.length - 1) {
          liveStream.push(frame);
          frame = '';
        }
      }

      frame = frame || frameStart;
      frame += (last || '');
    });
    liveStream.once('data',() => resolve());
  });
}

function noop() {
  // intentionally empty function
}

Python视频创建者和演示服务器:

#! /usr/bin/env python3

html = ('<title>picamera MJPEG streaming
demo</title>' +
'<style>' +                                                  '.live-image {' +
    'background-image: url(/live.mjpeg);' +
    'width: 640px;' +
    'height: 480px;' +
'}' +
'</style>' +

'<h1>PiCamera MJPEG Streaming Demo</h1>' +                   '<div class="live-image"></div>')

from io import BytesIO
from threading import Condition
from sys import argv,stdout                                 from picamera import PiCamera                                from time import sleep                                       from argparse import ArgumentParser                          from base64 import b64encode
from http import server

parser = ArgumentParser()

parser.add_argument('--serve','-s',action = 'store_true')

[serving] = vars(parser.parse_args()).values()

camera = PiCamera(resolution = '640x480',framerate = 24)

class StdoutStreamer():
    def write(self,raw):
    buf = b64encode(raw)
    img = buf.decode()
    stdout.write(img)

class ServerStreamer():
    buffer = BytesIO()
    condition = Condition()

    def write(self,buf):
        # New frame,copy the existing buffer's content and notify all
        # clients it's available
        self.buffer.truncate()
        with self.condition:
            self.frame = self.buffer.getvalue()
            self.condition.notify_all()
        self.buffer.seek(0)
        return self.buffer.write(buf)

serverOutput = ServerStreamer()                                                                                           class StreamingHandler(server.BaseHTTPRequestHandler):
    protocol_version = 'HTTP/1.1'

    def version_string(self):
        return ''

    def do_GET(self):
        if self.path == '/index.html' or self.path == '/':
            content = html.encode('utf-8')
            self.send_response(200)
            self.send_header('Content-Type','text/html; charset=utf-8')
            self.send_header('Content-Length',len(content))
            self.send_header('Connection','keep-alive')
            self.send_header('Keep-Alive','timeout=5')
            self.end_headers()
            self.wfile.write(content)
        elif self.path == '/live.mjpeg':
            self.send_response(200)
            self.send_header('Cache-Control','no-store')                self.send_header('Content-Type','multipart/x-mixed-replace; boundary=FRAME')
            self.end_headers()
            while True:
                with serverOutput.condition:
                    serverOutput.condition.wait()
                    frame = serverOutput.frame
            self.wfile.write(b'--FRAME\n')
            self.send_header('Content-Type','image/jpeg')
            self.send_header('Content-Length',len(frame)
            self.end_headers()
            self.wfile.write(frame)
            self.wfile.write(b'\n')
        else:
            self.send_error(404)
            self.end_headers()

class StreamingServer(server.ThreadingHTTPServer):
    allow_reuse_address = True
    daemon_threads = True

def serve():
    camera.start_recording(serverOutput,format = 'mjpeg')
    try:
        address = ('',8000)
        server = StreamingServer(address,StreamingHandler)
        server.serve_forever()
    finally:
        camera.stop_recording()

def startLive():
    output = StdoutStreamer()
    camera.start_recording(output,format = 'mjpeg')

    while True:
        sleep(1)

if __file__ == argv[0]:
    if serving:
        serve()
    else:
        startLive()

从HTTP标头到HTML页面,我使所有内容都尽可能相似且尽可能简单,以切出所有不必要的变量。我无法在Python中删除Server标头,因此我将其添加到了node中。抱歉,代码太长了,这是我可以将其归结为最短的时间。

出了什么问题?节点(或koa)是否无法正确转换Base64?是时间问题吗?

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。