mkv210_image.c文件详解

1 裸机程序Makefile介绍

led.bin: start.o 
    arm-linux-ld -Ttext 0x0 -o led.elf $^
    arm-linux-objcopy -O binary led.elf led.bin
    arm-linux-objdump -D led.elf > led_elf.dis
    gcc mkv210_image.c -o mkx210
    ./mkx210 led.bin 210.bin

%.o : %.S
    arm-linux-gcc -o $@ $< -c

%.o : %.c
    arm-linux-gcc -o $@ $< -c 

clean:
    rm *.o *.elf *.bin *.dis mkx210 -f


裸机程序中的Makefile(实际上真正的项目的Makefile都是这样的)是把程序的编译和链接过程分开的。(平时我们用gcc a.c -o exe这种方式来编译时,实际上把编译和链接过程一步完成了。在内部实际上编译和链接永远是分开独立进行的,编译要使用编译器gcc,链接要使用链接器ld)

链接器得到led.elf其实就是我们的可执行程序,(如果是在操作系统下,这个led.elf就可以执行了)但是在嵌入式裸机中我们需要的是可以烧写的文件(可烧写的文件就叫镜像image),因此我们需要用这个led.elf为原材料来制作镜像,制作工具是交叉编译工具链中的arm-linux-objcopy。

我们使用arm-linux-objdump工具进行反编译(反汇编),反汇编其实就是把编译后的elf格式的可执行程序给反过来的到对应的汇编程序,的到它的汇编源代码。

mkv210_image.c这个程序其实最终不是在开发板上执行的,而是在主机linux(就是用来执行make对整个项目进行编译的那个机器)中执行的,因此编译这个程序用gcc而不是用arm-linux-gcc。这个.c文件编译后得到一个可执行程序mkmini210,目的是通过执行这个mkmini210程序而由led.bin得到210.bin。(210.bin是通过SD卡启动时的裸机镜像,这个镜像需要由led.bin来加工的到,加工的具体方法和原理要看mkv210_image.c)

2 背景知识:S5PV210的启动过程回顾

分析启动过程可知:210启动后先执行内部iROM中的BL0,BL0执行完后会根据OMpin的配置选择一个外部设备来启动(有很多,我们实际使用的有2个:usb启动和SD卡启动)。在usb启动时内部BL0读取到BL1后不做校验,直接从BL1的实质内部0xd0020010开始执行,因此usb启动的镜像led.bin不需要头信息,因此我们从usb启动时直接将镜像下载到0xd0020010去执行即可,不管头信息了;从SD启动时,BL0会首先读取sd卡得到完整的镜像(完整指的是led.bin和16字节的头),然后BL0会自己根据你的实际镜像(指led.bin)来计算一个校验和checksum,然后和你完整镜像的头部中的checksum来比对。如果对应则执行BL1,如果不对应则启动失败(会转入执行2st启动,即SD2启动。如果这里已经是2st启动了,这里校验通不过就死定了)。

3 mkv210_image.c的作用:为BL1添加校验头

我们编译链接时只得到了led.bin,这个210.bin的得到和交叉编译工具链是完全无关的。由led.bin得到210.bin的过程是三星的S5PV210所特有的,因此需要我们自己去完成,为此我们写了mkv210_image.c来完成。

4 整个程序工作流分析

整个程序中首先申请一个16KB大小的buffer,然后把所有内容按照各自的位置填充进去,最终把填充好的buffer写入到一个文件(名叫210.bin)就形成了我们想要的镜像。

5 代码分析

/*
 * mkv210_image.c的主要作用就是由usb启动时使用的led.bin制作得到由sd卡启动的镜像210.bin
 *
 */
/* 在BL0阶段,Irom内固化的代码读取nandflash或SD卡前16K的内容,
 * 并比对前16字节中的校验和是否正确,正确则继续,错误则停止。
 */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define BUFSIZE                 (16*1024)
#define IMG_SIZE                (16*1024)
#define SPL_HEADER_SIZE         16
//#define SPL_HEADER              "S5PC110 HEADER  "
#define SPL_HEADER              "****************"

int main (int argc, char *argv[])
{
    FILE        *fp;
    char        *Buf, *a;
    int     BufLen;
    int     nbytes, fileLen;
    unsigned int    checksum, count;
    int     i;

    // 1. 3个参数
    if (argc != 3)
    {
        printf("Usage: %s <source file> <destination file>\n", argv[0]);
        return -1;
    }

    // 2. 分配16K的buffer
    BufLen = BUFSIZE;
    Buf = (char *)malloc(BufLen);
    if (!Buf)
    {
        printf("Alloc buffer failed!\n");
        return -1;
    }

    memset(Buf, 0x00, BufLen);

    // 3. 读源bin到buffer
    // 3.1 打开源bin
    fp = fopen(argv[1], "rb");//rb:二进制只读
    if( fp == NULL)
    {
        printf("source file open error\n");
        free(Buf);
        return -1;
    }
    // 3.2 获取源bin长度
    fseek(fp, 0L, SEEK_END);                                // 定位到文件尾
    fileLen = ftell(fp);                                    // 得到文件长度
    fseek(fp, 0L, SEEK_SET);                                // 再次定位到文件头
    // 3.3 源bin长度不得超过16K-16byte
    count = (fileLen < (IMG_SIZE - SPL_HEADER_SIZE))
        ? fileLen : (IMG_SIZE - SPL_HEADER_SIZE);
    // 3.4 buffer[0~15]存放"S5PC110 HEADER  "
    memcpy(&Buf[0], SPL_HEADER, SPL_HEADER_SIZE);
    // 3.5 读源bin到buffer[16]
    nbytes = fread(Buf + SPL_HEADER_SIZE, 1, count, fp);
    if ( nbytes != count )
    {
        printf("source file read error\n");
        free(Buf);
        fclose(fp);
        return -1;
    }
    fclose(fp);

    // 4. 计算校验和
    // 4.1 从第16byte开始统计buffer中共有几个1
    // 4.1 从第16byte开始计算,把buffer中所有的字节数据加和起来得到的结果
    a = Buf + SPL_HEADER_SIZE;
    for(i = 0, checksum = 0; i < IMG_SIZE - SPL_HEADER_SIZE; i++)
        checksum += (0x000000FF) & *a++;
    // 4.2 将校验和保存在buffer[8~15]
    a = Buf + 8;                            // Buf是210.bin的起始地址,+8表示向后位移2个字,也就是说写入到第3个字
    *( (unsigned int *)a ) = checksum;

    // 5. 拷贝buffer中的内容到目的bin
    // 5.1 打开目的bin
    fp = fopen(argv[2], "wb");
    if (fp == NULL)
    {
        printf("destination file open error\n");
        free(Buf);
        return -1;
    }
    // 5.2 将16k的buffer拷贝到目的bin中
    a = Buf;
    nbytes  = fwrite( a, 1, BufLen, fp);
    if ( nbytes != BufLen )
    {
        printf("destination file write error\n");
        free(Buf);
        fclose(fp);
        return -1;
    }

    free(Buf);
    fclose(fp);

    return 0;
}

5.1 代码详解

第1步:检验用户传参是不是3个。
第2步:分配16K Bbuffer并且填充为0.
第3步:·········

5.2 main函数两个形参的作用

main函数接收2个形参:argc和argv。

argc是用户(通过命令行来)执行这个程序时,实际传递的参数个数。注意这个个数是包含程序执行本身的。

argv是一个字符串数组,这个数组中存储的字符串就是一个个的传参。譬如我们执行程序时使用./mkx210 led.bin 210.bin,则argc = 3,则argv[0] = “./mkx210” ,argv[1] = “led.bin”, argv[2] = “210.bin”。

5.3 glibc读写文件接口

linux中要读取一个文件,可以使用fopen打开文件,fread读取文件,读完之后fclose关闭文件。要写文件用fwrite来写。这些函数是glibc的库函数,在linux中用man 3 可以查找。

5.4 校验和的计算方法

算法:校验和其实就是需要校验的内存区域中,所有内存中的内容按照字节为单位来进行相加,最终相加的和极为校验和。实现时大家要注意指针的类型为char *。

from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog, QMenu, QAction from ui.win import Ui_mainWindow from PyQt5.QtCore import Qt, QPoint, QTimer, QThread, pyqtSignal from PyQt5.QtGui import QImage, QPixmap, QPainter, QIcon import sys import os import json import numpy as np import torch import torch.backends.cudnn as cudnn import os import time import cv2 import warnings warnings.filterwarnings("ignore", category=DeprecationWarning) from models.experimental import attempt_load from utils.datasets import LoadImages, LoadWebcam from utils.CustomMessageBox import MessageBox from utils.general import check_img_size, check_requirements, check_imshow, colorstr, non_max_suppression, \ apply_classifier, scale_coords, xyxy2xywh, strip_optimizer, set_logging, increment_path # from utils.plots import colors, plot_one_box, plot_one_box_PIL from utils.plots import Annotator, colors, save_one_box from utils.torch_utils import select_device from utils.capnums import Camera from deep_sort.deep_sort import DeepSort def deepsort_update(Tracker, pred, xywh, np_img): outputs = Tracker.update(xywh, pred[:, 4:5], pred[:, 5].tolist(), cv2.cvtColor(np_img, cv2.COLOR_BGR2RGB)) return outputs class DetThread(QThread): send_img = pyqtSignal(np.ndarray) send_raw = pyqtSignal(np.ndarray) send_statistic = pyqtSignal(dict) send_msg = pyqtSignal(str) send_percent = pyqtSignal(int) send_fps = pyqtSignal(str) def __init__(self,tracker = 'ByteTrack',imgsz=(640,640)): super(DetThread, self).__init__() self.weights = './yolov5s.pt' self.current_weight = './yolov5s.pt' self.source = '0' self.conf_thres = 0.25 self.iou_thres = 0.45 self.jump_out = False # jump out of the loop self.is_continue = True # continue/pause self.percent_length = 1000 # progress bar self.rate_check = True # Whether to enable delay self.rate = 100 self.imgsz = check_img_size(imgsz) self.save_fold = './result' if tracker == 'DeepSort': self.tracker = DeepSort('deep_sort/deep_sort/deep/checkpoint/ckpt_car3.t7') self._type = 'DeepSort' @torch.no_grad() def run(self, imgsz=640, max_det=1000, # maximum detections per image device='', # cuda device, i.e. 0 or 0,1,2,3 or cpu view_img=True, # show results save_txt=False, # save results to *.txt save_conf=False, # save confidences in --save-txt labels save_crop=False, # save cropped prediction boxes nosave=False, # do not save images/videos classes=None, # filter by class: --class 0, or --class 0 2 3 agnostic_nms=False, # class-agnostic NMS augment=False, # augmented inference visualize=False, # visualize features update=False, # update all models project='runs/detect', # save results to project/name name='exp', # save results to project/name exist_ok=False, # existing project/name ok, do not increment line_thickness=3, # bounding box thickness (pixels) hide_labels=False, # hide labels hide_conf=False, # hide confidences half=False, # use FP16 half-precision inference ): # 初始化 try: # 选择设备(如CPU或GPU) device = select_device(device) # 如果设备不是CPU,设置half为True以使用半精度浮点数 half &= device.type != 'cpu' # 尝试加载模型,并将模型加载到指定设备上 model = attempt_load(self.weights, map_location=device) num_params = 0 # 计算模型参数的总数 for param in model.parameters(): num_params += param.numel() # 获取模型的步幅(stride),并确保图像尺寸是步幅的倍数 stride = int(model.stride.max()) imgsz = check_img_size(imgsz, s=stride) # 获取模型的类别名称 names = model.module.names if hasattr(model, 'module') else model.names # 如果使用半精度浮点数,转换模型的权重为半精度 if half: model.half() # 如果使用CUDA设备,进行一次空推理以初始化模型 if device.type != 'cpu': model(torch.zeros(1, 3, imgsz, imgsz).to(device).type_as(next(model.parameters()))) deepsort_tracker = DeepSort('deep_sort/deep_sort/deep/checkpoint/ckpt_car3.t7') # 数据加载 if self.source.isnumeric() or self.source.lower().startswith(('rtsp://', 'rtmp://', 'http://', 'https://')): view_img = check_imshow() cudnn.benchmark = True # set True to speed up constant image size inference dataset = LoadWebcam(self.source, img_size=imgsz, stride=stride) # bs = len(dataset) # batch_size else: dataset = LoadImages(self.source, img_size=imgsz, stride=stride) #根据输入源(视频流或本地图片/视频)加载数据。如果是视频流,启用 cudnn.benchmark 以加速推理 count = 0 jump_count = 0 start_time = time.time() dataset = iter(dataset) #主循环 while True: if self.jump_out: self.vid_cap.release() self.send_percent.emit(0) self.send_msg.emit('停止') if hasattr(self, 'out'): self.out.release() break #主循环处理每一帧数据。如果接收到跳出信号,则释放资源并退出循环 # 更换模型 # 如果当前权重与新权重不一致,更新模型 if self.current_weight != self.weights: # 加载模型 model = attempt_load(self.weights, map_location=device) # 加载FP32模型(32位浮点数) num_params = 0 # 计算模型参数总数 for param in model.parameters(): num_params += param.numel() # 获取模型步幅(stride) stride = int(model.stride.max()) # 检查图像尺寸是否为步幅的整数倍 imgsz = check_img_size(imgsz, s=stride) # 获取模型的类别名称 names = model.module.names if hasattr(model, 'module') else model.names # 如果使用半精度浮点数,转换模型权重为FP16(16位浮点数) if half: model.half() # 如果设备不是CPU,运行一次模型以完成初始化 if device.type != 'cpu': model(torch.zeros(1, 3, imgsz, imgsz).to(device).type_as(next(model.parameters()))) # 更新当前权重 self.current_weight = self.weights #如果检测到模型权重发生改变,重新加载模型。 #数据预处理 # 如果继续处理标志位为True,则执行以下代码 if self.is_continue: # 从数据集中获取下一帧数据,包括路径、图像、原始图像和视频捕获对象 path, img, im0s, self.vid_cap = next(dataset) count += 1 # 帧计数器加一 # 每处理30帧,计算一次FPS(帧率) if count % 30 == 0 and count >= 30: fps = int(30 / (time.time() - start_time)) # 计算30帧处理所需时间的平均FPS self.send_fps.emit('fps:' + str(fps)) # 发送FPS信号 start_time = time.time() # 重新记录开始时间 # 如果有视频捕获对象,计算视频处理进度百分比 if self.vid_cap: percent = int(count / self.vid_cap.get(cv2.CAP_PROP_FRAME_COUNT) * self.percent_length) self.send_percent.emit(percent) # 发送进度百分比信号 else: percent = self.percent_length # 如果没有视频捕获对象,设定进度为满 # 初始化统计字典,统计每个类别的数量 statistic_dic = {name: 0 for name in names} # 将图像从numpy数组转换为torch张量,并加载到指定设备(如GPU) img = torch.from_numpy(img).to(device) # 如果使用半精度浮点数,转换图像数据类型,否则转换为浮点数 img = img.half() if half else img.float() # uint8 to fp16/32 img /= 255.0 # 将像素值从0-255归一化到0.0-1.0 # 如果图像是三维的,增加一个维度以匹配模型输入要求 if img.ndimension() == 3: img = img.unsqueeze(0) # 推理 pred = model(img, augment=augment)[0] #NMS pred = non_max_suppression(pred, self.conf_thres, self.iou_thres, classes, agnostic_nms, max_det=max_det) # 处理检测结果 迭代每个检测结果,并绘制边界框和标签 # 遍历每张图像的检测结果 for i, det in enumerate(pred): # detections per image # 复制原始图像,以便在上面绘制标注 im0 = im0s.copy() # 创建一个Annotator对象,用于在图像上绘制标注 annotator = Annotator(im0, line_width=line_thickness, example=str(names)) # 如果有检测结果 if len(det): # 将检测框的坐标从img_size尺度重新缩放到原始图像尺度,并四舍五入 det[:, :4] = scale_coords(img.shape[2:], det[:, :4], im0.shape).round() # 进行目标跟踪 track_outputs = deepsort_update(deepsort_tracker, det.cpu(), det[:, :4].cpu(), im0) # Write results # for *xyxy, conf, cls in reversed(det): # c = int(cls) # integer class # statistic_dic[names[c]] += 1 # label = None if hide_labels else (names[c] if hide_conf else f'{names[c]} {conf:.2f}') # annotator.box_label(xyxy, label, color=colors(c, True)) # 将跟踪结果绘制到图片上 if len(track_outputs)>0: for track_output in track_outputs: xyxy = track_output[:4] c = int(track_output[4]) # integer class track_id = 'ID_' + str(track_output[5]) statistic_dic[names[c]] += 1 label = (f'{track_id}') if hide_labels else (f'{track_id} {names[c]}') annotator.box_label(xyxy, label, color=colors(c, True)) if self.rate_check: time.sleep(1/self.rate) #保存检测结果图像或视频,并通过信号发送处理后的图像和统计数据 im0 = annotator.result() self.send_img.emit(im0) self.send_raw.emit(im0s if isinstance(im0s, np.ndarray) else im0s[0]) self.send_statistic.emit(statistic_dic) if self.save_fold: os.makedirs(self.save_fold, exist_ok=True) if self.vid_cap is None: save_path = os.path.join(self.save_fold, time.strftime('%Y_%m_%d_%H_%M_%S', time.localtime()) + '.jpg') cv2.imwrite(save_path, im0) else: if count == 1: ori_fps = int(self.vid_cap.get(cv2.CAP_PROP_FPS)) if ori_fps == 0: ori_fps = 25 # width = int(self.vid_cap.get(cv2.CAP_PROP_FRAME_WIDTH)) # height = int(self.vid_cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) width, height = im0.shape[1], im0.shape[0] save_path = os.path.join(self.save_fold, time.strftime('%Y_%m_%d_%H_%M_%S', time.localtime()) + '.mp4') self.out = cv2.VideoWriter(save_path, cv2.VideoWriter_fourcc(*"mp4v"), ori_fps, (width, height)) self.out.write(im0) if percent == self.percent_length: print(count) self.send_percent.emit(0) self.send_msg.emit('结束') if hasattr(self, 'out'): self.out.release() break except Exception as e: self.send_msg.emit('%s' % e) class MainWindow(QMainWindow, Ui_mainWindow): def __init__(self, parent=None): super(MainWindow, self).__init__(parent) self.setupUi(self) self.m_flag = False # style 1: window can be stretched # self.setWindowFlags(Qt.CustomizeWindowHint | Qt.WindowStaysOnTopHint) # style 2: window can not be stretched self.setWindowFlags(Qt.Window | Qt.FramelessWindowHint | Qt.WindowSystemMenuHint | Qt.WindowMinimizeButtonHint | Qt.WindowMaximizeButtonHint) # self.setWindowOpacity(0.85) # Transparency of window self.minButton.clicked.connect(self.showMinimized) self.maxButton.clicked.connect(self.max_or_restore) # show Maximized window self.maxButton.animateClick(10) self.closeButton.clicked.connect(self.close) self.qtimer = QTimer(self) self.qtimer.setSingleShot(True) self.qtimer.timeout.connect(lambda: self.statistic_label.clear()) # search models automatically self.comboBox.clear() self.pt_list = os.listdir('./pt') self.pt_list = [file for file in self.pt_list if file.endswith('.pt')] self.pt_list.sort(key=lambda x: os.path.getsize('./pt/'+x)) self.comboBox.clear() self.comboBox.addItems(self.pt_list) self.qtimer_search = QTimer(self) self.qtimer_search.timeout.connect(lambda: self.search_pt()) self.qtimer_search.start(2000) # yolov5 thread self.det_thread = DetThread() self.model_type = self.comboBox.currentText() self.det_thread.weights = "./pt/%s" % self.model_type self.det_thread.source = '0' self.det_thread.percent_length = self.progressBar.maximum() self.det_thread.send_raw.connect(lambda x: self.show_image(x, self.raw_video)) self.det_thread.send_img.connect(lambda x: self.show_image(x, self.out_video)) self.det_thread.send_statistic.connect(self.show_statistic) self.det_thread.send_msg.connect(lambda x: self.show_msg(x)) self.det_thread.send_percent.connect(lambda x: self.progressBar.setValue(x)) self.det_thread.send_fps.connect(lambda x: self.fps_label.setText(x)) self.fileButton.clicked.connect(self.open_file) self.cameraButton.clicked.connect(self.chose_cam) #self.rtspButton.clicked.connect(self.chose_rtsp) self.runButton.clicked.connect(self.run_or_continue) self.stopButton.clicked.connect(self.stop) self.comboBox.currentTextChanged.connect(self.change_model) self.confSpinBox.valueChanged.connect(lambda x: self.change_val(x, 'confSpinBox')) self.confSlider.valueChanged.connect(lambda x: self.change_val(x, 'confSlider')) self.iouSpinBox.valueChanged.connect(lambda x: self.change_val(x, 'iouSpinBox')) self.iouSlider.valueChanged.connect(lambda x: self.change_val(x, 'iouSlider')) self.rateSpinBox.valueChanged.connect(lambda x: self.change_val(x, 'rateSpinBox')) self.rateSlider.valueChanged.connect(lambda x: self.change_val(x, 'rateSlider')) self.checkBox.clicked.connect(self.checkrate) self.saveCheckBox.clicked.connect(self.is_save) self.load_setting() def search_pt(self): pt_list = os.listdir('./pt') pt_list = [file for file in pt_list if file.endswith('.pt')] pt_list.sort(key=lambda x: os.path.getsize('./pt/' + x)) if pt_list != self.pt_list: self.pt_list = pt_list self.comboBox.clear() self.comboBox.addItems(self.pt_list) def is_save(self): if self.saveCheckBox.isChecked(): self.det_thread.save_fold = './result' else: self.det_thread.save_fold = None def checkrate(self): if self.checkBox.isChecked(): self.det_thread.rate_check = True else: self.det_thread.rate_check = False def chose_cam(self): try: self.stop() MessageBox( self.closeButton, title='提示', text='加载摄像头', time=2000, auto=True).exec_() # get the number of local cameras _, cams = Camera().get_cam_num() popMenu = QMenu() popMenu.setFixedWidth(self.cameraButton.width()) popMenu.setStyleSheet(''' QMenu { font-size: 16px; font-family: "Microsoft YaHei UI"; font-weight: light; color:white; padding-left: 5px; padding-right: 5px; padding-top: 4px; padding-bottom: 4px; border-style: solid; border-width: 0px; border-color: rgba(255, 255, 255, 255); border-radius: 3px; background-color: rgba(200, 200, 200,50);} ''') for cam in cams: exec("action_%s = QAction('%s')" % (cam, cam)) exec("popMenu.addAction(action_%s)" % cam) x = self.groupBox_5.mapToGlobal(self.cameraButton.pos()).x() y = self.groupBox_5.mapToGlobal(self.cameraButton.pos()).y() y = y + self.cameraButton.frameGeometry().height() pos = QPoint(x, y) action = popMenu.exec_(pos) if action: self.det_thread.source = action.text() self.statistic_msg('加载视频:{}'.format(action.text())) except Exception as e: self.statistic_msg('%s' % e) def load_setting(self): config_file = 'config/setting.json' if not os.path.exists(config_file): iou = 0.26 conf = 0.33 rate = 10 check = 0 savecheck = 0 new_config = {"iou": iou, "conf": conf, "rate": rate, "check": check, "savecheck": savecheck } new_json = json.dumps(new_config, ensure_ascii=False, indent=2) with open(config_file, 'w', encoding='utf-8') as f: f.write(new_json) else: config = json.load(open(config_file, 'r', encoding='utf-8')) if len(config) != 5: iou = 0.26 conf = 0.33 rate = 10 check = 0 savecheck = 0 else: iou = config['iou'] conf = config['conf'] rate = config['rate'] check = config['check'] savecheck = config['savecheck'] self.confSpinBox.setValue(conf) self.iouSpinBox.setValue(iou) self.rateSpinBox.setValue(rate) self.checkBox.setCheckState(check) self.det_thread.rate_check = check self.saveCheckBox.setCheckState(savecheck) self.is_save() def change_val(self, x, flag): if flag == 'confSpinBox': self.confSlider.setValue(int(x*100)) elif flag == 'confSlider': self.confSpinBox.setValue(x/100) self.det_thread.conf_thres = x/100 elif flag == 'iouSpinBox': self.iouSlider.setValue(int(x*100)) elif flag == 'iouSlider': self.iouSpinBox.setValue(x/100) self.det_thread.iou_thres = x/100 elif flag == 'rateSpinBox': self.rateSlider.setValue(x) elif flag == 'rateSlider': self.rateSpinBox.setValue(x) self.det_thread.rate = x * 10 else: pass def statistic_msg(self, msg): self.statistic_label.setText(msg) # self.qtimer.start(3000) def show_msg(self, msg): self.runButton.setChecked(Qt.Unchecked) self.statistic_msg(msg) if msg == "Finished": self.saveCheckBox.setEnabled(True) def change_model(self, x): self.model_type = self.comboBox.currentText() self.det_thread.weights = "./pt/%s" % self.model_type self.statistic_msg('Change model to %s' % x) def open_file(self): config_file = 'config/fold.json' # config = json.load(open(config_file, 'r', encoding='utf-8')) config = json.load(open(config_file, 'r', encoding='utf-8')) open_fold = config['open_fold'] if not os.path.exists(open_fold): open_fold = os.getcwd() name, _ = QFileDialog.getOpenFileName(self, 'Video/image', open_fold, "Pic File(*.mp4 *.mkv *.avi *.flv " "*.jpg *.png)") if name: self.det_thread.source = name self.statistic_msg('Loaded file:{}'.format(os.path.basename(name))) config['open_fold'] = os.path.dirname(name) config_json = json.dumps(config, ensure_ascii=False, indent=2) with open(config_file, 'w', encoding='utf-8') as f: f.write(config_json) self.stop() def max_or_restore(self): if self.maxButton.isChecked(): self.showMaximized() else: self.showNormal() def run_or_continue(self): self.det_thread.jump_out = False if self.runButton.isChecked(): self.saveCheckBox.setEnabled(False) self.det_thread.is_continue = True if not self.det_thread.isRunning(): self.det_thread.start() source = os.path.basename(self.det_thread.source) source = 'camera' if source.isnumeric() else source self.statistic_msg('正在检测 >> 模型:{},文件:{}'. format(os.path.basename(self.det_thread.weights), source)) else: self.det_thread.is_continue = False self.statistic_msg('暂停') def stop(self): self.det_thread.jump_out = True self.saveCheckBox.setEnabled(True) def mousePressEvent(self, event): self.m_Position = event.pos() if event.button() == Qt.LeftButton: if 0 < self.m_Position.x() < self.groupBox.pos().x() + self.groupBox.width() and \ 0 < self.m_Position.y() < self.groupBox.pos().y() + self.groupBox.height(): self.m_flag = True def mouseMoveEvent(self, QMouseEvent): if Qt.LeftButton and self.m_flag: self.move(QMouseEvent.globalPos() - self.m_Position) def mouseReleaseEvent(self, QMouseEvent): self.m_flag = False @staticmethod def show_image(img_src, label): try: ih, iw, _ = img_src.shape w = label.geometry().width() h = label.geometry().height() # keep original aspect ratio if iw/w > ih/h: scal = w / iw nw = w nh = int(scal * ih) img_src_ = cv2.resize(img_src, (nw, nh)) else: scal = h / ih nw = int(scal * iw) nh = h img_src_ = cv2.resize(img_src, (nw, nh)) frame = cv2.cvtColor(img_src_, cv2.COLOR_BGR2RGB) img = QImage(frame.data, frame.shape[1], frame.shape[0], frame.shape[2] * frame.shape[1], QImage.Format_RGB888) label.setPixmap(QPixmap.fromImage(img)) except Exception as e: print(repr(e)) def show_statistic(self, statistic_dic): try: self.resultWidget.clear() statistic_dic = sorted(statistic_dic.items(), key=lambda x: x[1], reverse=True) statistic_dic = [i for i in statistic_dic if i[1] > 0] results = [' '+str(i[0]) + ':' + str(i[1]) for i in statistic_dic] self.resultWidget.addItems(results) except Exception as e: print(repr(e)) def closeEvent(self, event): self.det_thread.jump_out = True config_file = 'config/setting.json' config = dict() config['iou'] = self.confSpinBox.value() config['conf'] = self.iouSpinBox.value() config['rate'] = self.rateSpinBox.value() config['check'] = self.checkBox.checkState() config['savecheck'] = self.saveCheckBox.checkState() config_json = json.dumps(config, ensure_ascii=False, indent=2) with open(config_file, 'w', encoding='utf-8') as f: f.write(config_json) MessageBox( self.closeButton, title='提示', text='正在关闭程序', time=2000, auto=True).exec_() sys.exit(0) if __name__ == "__main__": app = QApplication(sys.argv) myWin = MainWindow() myWin.show() # myWin.showMaximized() sys.exit(app.exec_())
最新发布
05-30
<think>嗯,用户在使用FFmpeg合并视频和音频时遇到了问题,生成的MKV文件没有画面。我需要仔细分析他的命令,看看哪里出错了。 首先,用户运行的命令是: ffmpeg -i video_only.mkv -i new_audio.aac -map 0:v -map 1:a final_output.mkv 看起来他试图将video_only.mkv的视频和new_audio.aac的音频合并到final_output.mkv中。但结果没有画面,可能的原因有几个。 首先,检查输入文件是否存在问题。可能video_only.mkv本身就没有视频流,或者视频流损坏。这时候需要用户用ffprobe检查一下这个文件的结构,确认是否有视频流。 然后,检查-map选项的使用是否正确。-map 0:v应该选择第一个输入文件(video_only.mkv)的所有视频流,而-map 1:a选择第二个输入文件(new_audio.aac)的所有音频流。不过,如果第二个输入文件没有音频流,或者格式不支持,也可能导致问题。例如,如果new_audio.aac实际上不是音频文件,或者扩展名错误,FFmpeg可能无法正确识别。 另外,编解码器复制的问题也可能存在。原命令中没有指定编解码器参数,FFmpeg默认会重新编码。但如果输入视频的格式与输出容器不兼容,可能导致问题。例如,某些视频编码可能不适合MKV容器,或者需要特定的参数。这时候用户需要确保输出格式支持输入的视频编码,或者显式指定-c:v copy来避免重新编码,同时检查是否有错误提示。 还有可能是输出容器的问题。虽然MKV通常支持多种编码,但某些情况下可能需要特定的设置。用户应该检查FFmpeg的执行输出,看看是否有错误或警告信息,比如无法识别的流或者不支持的编码格式。 此外,用户可能没有注意到输入文件的顺序。-i参数的顺序决定了输入文件的索引,map选项是否正确引用了这些索引。比如,video_only.mkv是输入0,new_audio.aac是输入1,确保-map 0:v正确指向视频文件。 最后,可能缺少了其他必要的流,比如字幕或数据流,但用户可能只关心视频和音频,所以问题可能不在这里。但有时候如果输入文件有其他流未被处理,FFmpeg可能会有默认行为,不过通常不影响主要流的处理。 总结可能的原因:输入文件问题、map选项错误、编解码器不兼容、容器支持问题,或者缺少必要的参数。建议用户检查输入文件的有效性,确认map选项正确,检查FFmpeg的输出日志,并考虑显式指定编解码器参数。</think>以下是对命令 `ffmpeg -i video_only.mkv -i new_audio.aac -map 0:v -map 1:a final_output.mkv` **未生成画面**的可能原因及解决方法: --- ### **1. 输入文件问题** - **现象**:`video_only.mkv` 文件本身可能缺少有效视频流(例如文件损坏或错误生成)。 - **检查方法**: 使用 `ffprobe` 工具验证输入文件的流信息: ```bash ffprobe video_only.mkv ``` 若输出中没有 `Video: ...` 的行,则说明文件无有效视频流。 --- ### **2. `-map` 参数错误** - **问题**:`-map 0:v` 表示从第一个输入文件(`video_only.mkv`)提取所有视频流,`-map 1:a` 表示从第二个输入文件(`new_audio.aac`)提取所有音频流。 - **可能错误**: - 如果 `video_only.mkv` 中没有视频流(例如文件名错误,实际是音频文件),则 `-map 0:v` 会失效。 - 如果 `new_audio.aac` 中没有音频流,`-map 1:a` 也会失效,但此问题不会影响视频流。 --- ### **3. 编解码器未正确指定** - **默认行为**:如果未通过 `-c:v` 或 `-c:a` 指定编解码器,FFmpeg 会尝试自动选择编解码器(可能导致意外结果)。 - **解决方法**: 显式指定视频和音频的编解码器为 `copy`(直接复制,避免重新编码): ```bash ffmpeg -i video_only.mkv -i new_audio.aac -map 0:v -map 1:a -c:v copy -c:a copy final_output.mkv ``` - `-c:v copy`:直接复制视频流。 - `-c:a copy`:直接复制音频流。 --- ### **4. 容器格式兼容性问题** - **现象**:输入视频流的编码格式(如 H.265)可能与 MKV 容器存在兼容性问题(极少见)。 - **解决方法**: - 尝试重新封装为其他容器(如 MP4): ```bash ffmpeg -i video_only.mkv -i new_audio.aac -map 0:v -map 1:a -c:v copy -c:a copy final_output.mp4 ``` - 检查 FFmpeg 输出的警告信息(如 `[matroska @ ...] 不支持的编码格式`)。 --- ### **5. 检查 FFmpeg 输出日志** - **关键步骤**:运行命令时,观察 FFmpeg 的输出日志。 - 如果显示 `Stream map '0:v' matches no streams`,说明 `video_only.mkv` 无视频流。 - 如果显示 `Failed to open input file 'video_only.mkv'`,说明文件路径错误。 --- ### **完整修复命令示例** ```bash ffmpeg -i video_only.mkv -i new_audio.aac -map 0:v -map 1:a -c:v copy -c:a copy final_output.mkv ``` --- ### **其他可能性** 1. **权限问题**:输出目录无写入权限。 2. **文件覆盖冲突**:已有 `final_output.mkv` 文件被占用。 3. **版本问题**:FFmpeg 版本过旧(建议升级到最新版)。 如果问题仍存在,请提供完整的 **FFmpeg 输出日志** 以便进一步分析!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值