无网络数据传输方案

无网络数据传输方案

前言

  • 在日常生活中,传输数据的方式有很多种。其中最普遍的就是网络传输,流媒体平台、局域网联机、面对面互传文件、蓝牙文件传输等都属于网络传输。也有一些无需网络的方式,比如常见的NFC,二维码等。但是NFC能够存储的数据量非常有限,在50字节左右。相比之下二维码容量更大,最大为2048字节,但是也不够存储音视频文件。

  • 现在我们有三个问题:

    1. 用什么存储
    2. 如何编码文件
    3. 如何解码文件
  • 先回答第一个,我们使用二维码存储数据(如果有其它更合适的也可以替换)。下面我们研究其它两个问题。

如何编码文件

  • 在选择了二维码后,第二个问题就出现了,我们的二维码不足以编码文件,我们很自然可以想到拆分文件,每个片段生成一个二维码。这是传统网络传输会做的事情,在拆分后会出现片段乱序、片段缺失等问题,因此我们还需要一些校验信息和文件信息。这样我们就知道编码要怎么做了。

文件信息

  • 我们可以在第一张二维码中存储文件信息,包括文件名、文件类型、文件大小、片段数等,我们还可以用一个signal字段区分文件信息二维码和文件内容二维码。

  • 下面是文件信息二维码存储的内容:

  • {
        'num': 0,
        'signal': '<start>',
        'content': file.name,
        'filesize': file.size,
        'n_chunks': n_chunks,
    }
    
  • 而文件片段存储的信息如下:

  • {
        'num': idx,
        'signal': '<content>',
        'content': base64str
    }
    

生成二维码

  • 下面我们简单看一下二维码生成的代码。这里使用qrcode模块:

  • pip install qrcode
    
  • 我们不能直接序列化带二进制内容的json,因此将二进制内容转换成base64,代码如下:

  • import json
    import base64
    import qrcode
    
    chunk = {
        'num': 0,
        'signal': '<content>',
        'content': base64.b64encode(b'xxx').decode('utf-8')
    }
    data = json.dumps(chunk)
    qrcode.make(data).save('test.jpg')
    

对文件编码

  • 下面我们对文件拆分并编码。这里我们每次读取固定大小的片段并编码成二维码:

  • import os
    import json
    import math
    import qrcode
    import base64
    from tqdm import tqdm
    
    # 文件信息
    chunk_size = 1024 + 512
    file_path = '111.mp3'
    file_size = os.path.getsize(file_path)
    chunks = math.ceil(file_size / chunk_size)
    
    # 创建保存二维码的目录
    os.makedirs('qrs', exist_ok=True)
    
    qrcode.make(json.dumps({
        'num': 0,
        'signal': '<start>',
        'content': '111.mp3',
        'filesize': file_size,
        'n_chunks': chunks
    })).save(f'qrs/0.jpg')
    with open(file_path, 'rb') as f:
        pbar = tqdm(total=chunks)
        while buffer := f.read(chunk_size):
            qrcode.make(json.dumps({
                'num': pbar.n + 1,
                'signal': '<content>',
                'content': base64.b64encode(buffer).decode('utf-8')
            })).save(f'qrs/{pbar.n + 1}.jpg')
            pbar.update(1)
    

编写UI

  • 下面我们用streamlit编写一个简单的页面,streamlit的使用参见官网:streamlit.io/

  • 代码如下:

  • # run with
    # streamlit run app.py --server.enableXsrfProtection false
    import json
    import math
    import uuid
    import base64
    from pathlib import Path
    
    import qrcode
    import streamlit as st
    
    chunk_size = 1024 + 512
    n_cols = 2
    col_size = 350
    
    tab1, tab2 = st.tabs(['上传文件', '下载文件'])
    with tab1:
        st.header('上传文件')
        if file := st.file_uploader('上传文件进行编码'):
            tmp_filename = f'{uuid.uuid4().hex}.{file.name.split(".")[-1]}'
            # 创建目录
            n_chunks = math.ceil(file.size / chunk_size)
            dst_path = Path('qrs') / Path(file.name).stem
            dst_path.mkdir(exist_ok=True, parents=True)
            idx = 0
            # 编码文件的meta信息
            qrcode.make(
                json.dumps({
                    'num': idx,
                    'signal': '<start>',
                    'content': file.name,
                    'filesize': file.size,
                    'n_chunks': n_chunks,
                }, ensure_ascii=False)
            ).save(dst_path / f'{idx:08}.jpg')
            idx += 1
            pbar = st.progress(0 / n_chunks, text='Encoding File')
            while buffer := file.read(chunk_size):
                base64buffer = base64.b64encode(buffer).decode('utf-8')
                qrcode.make(json.dumps({
                    'num': idx,
                    'signal': '<content>',
                    'content': base64buffer
                })).save(dst_path / f'{idx:08}.jpg')
                pbar.progress(idx / n_chunks)
                idx += 1
            st.success('Upload Success!')
    
    with tab2:
        st.header('下载文件')
        dirs = Path('qrs').iterdir()
        if d := st.selectbox('Choose the file', dirs):
            cols = st.columns(n_cols)
            for idx, file in enumerate(Path(d).glob('*.[jpJP][pnPN][gG]')):
                cols[idx % n_cols].image(str(file), width=col_size)
                cols[idx % n_cols].text(idx)
    
  • 上面我们创建了两个tab,分别是上编码文件和下载文件。界面效果如下:

  • 在这里插入图片描述

如何解码文件

  • 正常情况下,我们不能通过扫描下载一个文件,扫码只返回一串字符串。如果想通过扫描下载文件,则需要编写一个特殊的客户端。在知道编码过程后,解码过程就简单了。

识别二维码

  • 解码正常的做法是编写一个手机app,调用摄像头完成,这里为了方便就编写py脚本通过读取本地文件完成,我们先看识别二维码的操作。这里需要使用pyzbar模块:

  • pip install pyzbar
    pip install opencv-python
    
  • 识别代码如下:

  • import cv2
    from pyzbar import pyzbar
    img = cv2.imread('')
    decoded = pyzbar.decode(img)[0].data
    

解码文件

  • 下面就是从二维码中解码出文件。代码如下:

  • import json
    import base64
    from pathlib import Path
    
    import cv2
    from pyzbar import pyzbar
    
    filename = ''
    n_chunks = -1
    contents = []
    file_generator = Path('qrs/545bc9adcc8e48a88c45d4a1305b5e47').glob('*.jpg')
    while file := next(file_generator):
        if n_chunks != -1 and n_chunks == len(contents):
            with open(filename, 'ab') as fp:
                contents.sort(key=lambda x: int(x['num']))
                for data in contents:
                    fp.write(data['content'])
            break
        img = cv2.imread(str(file))
        data = json.loads(pyzbar.decode(img)[0].data)
        if data['signal'] == '<start>':
            filename = data['content']
            n_chunks = data['n_chunks']
        else:
            data['content'] = base64.b64decode(data['content'].encode('utf-8'))
            contents.append(data)
    

讨论

改进

  • 在多数情况下,用二维码存储文件不是一个好的选择,存储一个2秒的音频需要大概70张二维码。占用的空间远比音频本身大,而且在编码解码过程要花费更多时间。对此我们有一些可以改进的地方。
  • 1)压缩文件
  • 第一件可以尝试做的事情就是压缩文件,比如图像可以采用JPEG格式。
  • (2)动图存储
  • 第二个尝试可以是将二维码序列转换成GIF,这样更多是方便管理二维码序列。
  • (3)改进压缩算法
  • 在我们的例子中,我们已经不能直接用常规二维码扫描算法得出文件结果,因此我们也可以不遵守二维码编码规则,定义一种压缩度更高的编码算法。具体可以参考:github.com/sz3/libcimb…

应用

  • 从上面的结果来看,二维码传输方案似乎不是很实用,但是还是有一些新奇的用法的。

  • 在纸质书中,我们的想法是只能传达文字、图像信息,当我们提到文字可以传递音频信息时,会想到下面的例子:

  • 你干嘛~哎哟
    鸡你太美
    
  • 但是这种音频只存在我们的想象。现在利用二维码编码音频,打印到书本上,通过扫码我们就可以真正听到声音。同样我们还可以存储视频等信息。基于这一想法,我们可以做出许多有趣的东西。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IT枫斗者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值