1.直接下载封装后的python模块
如果你并不关心内部的技术细节,可以直接下载封装好的python模块:
https://download.csdn.net/download/twicave/89594277
2.源码和原理说明
下面包含了所有的代码, 你可以自行组装:
2.0 目录结构:
4.0K ./sub/fetch_from_stream.py
4.0K ./sub/get_h265_resolution_of_stream.sh
3.6M ./sub/frame_by_soft_decode_of_h265file.bin
4.0K ./sub/get_h265_resolution_of_file.sh
500K ./sub/01.h265
16K ./sub/get_h265_resolution
8.0K ./sub/get_h265_resolution.c
4.0K ./sub/makefile
4.2M ./sub
4.0K ./gp_get_h265_resolution.py ...............................需要调用的模块
2.6M ./Tennis1080p.h265 .............................................一个.h265文件示例
6.7M .
2.1 外层py invoker代码:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# 获取当前脚本文件所在目录的父目录,并构建相对路径
import os
import sys
current_dir = os.path.dirname(os.path.abspath(__file__))
project_path = os.path.join(current_dir, '..')
sys.path.append(project_path)
sys.path.append(current_dir)
import subprocess
def gp_h265_get_real_solution_of_file(file_path):
# 运行 .sh 脚本并获取输出
result = subprocess.run(['bash', 'sub/get_h265_resolution_of_file.sh', file_path], capture_output=True, text=True)
# 获取标准输出
output = result.stdout.strip()
print(output)
# 分割输出字符串,并转换为数字
numbers = [int(num) for num in output.split(',')]
return numbers
def gp_h265_get_real_solution_of_stream(rtsp_path):
# 运行 .sh 脚本并获取输出
result = subprocess.run(['bash', 'sub/get_h265_resolution_of_stream.sh', rtsp_path], capture_output=True, text=True)
# 获取标准输出
output = result.stdout.strip()
# 分割输出字符串,并转换为数字
numbers = [int(num) for num in output.split(',')]
return numbers
#usage:
if __name__ == "__main__":
file_path = "./Tennis1080p.h265"
print(gp_h265_get_real_solution_of_file(file_path))
rtsp_path = "rtsp://admin:a1234567@192.168.0.6:554/h265/ch1/main/av_stream"
print(gp_h265_get_real_solution_of_stream(file_path))
2.2 sh代码
两个.sh文件原本接口不同,后来修改时代码统一了。
基本逻辑:
- 先截取1帧 =>得到视在分辨率width, height
- 将该帧转为.h265
- 得到.h265的内部缓冲分辨率 =>输出[disp_width, disp_height, real_width, real_height]四元组
#!/bin/bash
# 获取脚本所在的目录
SCRIPT_DIR=$(dirname "$(realpath "$0")")
# 切换到脚本所在的目录
cd "$SCRIPT_DIR" || exit
file_of_h265=$1
# 运行程序并捕获输出
output=$(python3 ./fetch_from_stream.py "$file_of_h265")
# 解析输出结果,假设输出格式为 "1920,1080"
IFS=',' read -r width height <<< "$output"
mpi_enc_test -i frame_by_soft_decode_of_h265file.bin -w $width -h $height -t 16777220 -o 01.h265 -n 20
./get_h265_resolution ./01.h265
#!/bin/bash
# 获取脚本所在的目录
SCRIPT_DIR=$(dirname "$(realpath "$0")")
# 切换到脚本所在的目录
cd "$SCRIPT_DIR" || exit
rtsp_addr=$1
# 运行程序并捕获输出
output=$(python3 ./fetch_from_stream.py "$rtsp_addr")
# 解析输出结果,假设输出格式为 "1920,1080"
IFS=',' read -r width height <<< "$output"
mpi_enc_test -i frame_by_soft_decode_of_h265file.bin -w $width -h $height -t 16777220 -o 01.h265 -n 20
./get_h265_resolution ./01.h265
2.3 从rtsp源截取一帧图像的代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# 获取当前脚本文件所在目录的父目录,并构建相对路径
import os
import sys
current_dir = os.path.dirname(os.path.abspath(__file__))
project_path = os.path.join(current_dir, '..')
sys.path.append(project_path)
sys.path.append(current_dir)
import gi
import cv2
import threading
import time
import warnings
import queue
import sys
gi.require_version('Gst', '1.0')
from gi.repository import Gst, GLib
def on_new_sample(sink):
sample = sink.emit('pull-sample')
buffer = sample.get_buffer()
# 检查 buffer 是否有效
if buffer:
caps = sample.get_caps()
structure = caps.get_structure(0)
width = structure.get_int('width')[1]
height = structure.get_int('height')[1]
print(f"{width}, {height}\n")
'''
print(f'{structure}')
'''
# 创建一个 mapinfo 对象以映射数据
success, mapinfo = buffer.map(Gst.MapFlags.READ)
if success:
# 获取映射的字节数据
data = mapinfo.data
#print(f'frame len ={len(data)}')
# 文件路径
file_path = 'frame_by_soft_decode_of_h265file.bin'
# 打开文件并写入数据
with open(file_path, 'wb') as file:
file.write(data)
# 取消映射
buffer.unmap(mapinfo)
sys.exit(0)
return Gst.FlowReturn.OK
def fetch_a_frame_from_file(filepath):
Gst.init(None)
pipeline = Gst.parse_launch(f'filesrc location={filepath} ! h265parse ! mppvideodec ! videoconvert ! appsink emit-signals=True max-buffers=1 drop=True sync=False')
appsink = pipeline.get_by_name('appsink0')
appsink.connect('new-sample', on_new_sample)
pipeline.set_state(Gst.State.PLAYING)
GLib.MainLoop().run()
def fetch_a_frame_from_stream(rtsp_addr):
Gst.init(None)
pipeline = Gst.parse_launch(f'rtspsrc location={rtsp_addr} latency=200 ! rtph265depay ! h265parse ! mppvideodec ! videoconvert ! appsink emit-signals=True max-buffers=1 drop=True sync=False')
appsink = pipeline.get_by_name('appsink0')
appsink.connect('new-sample', on_new_sample)
pipeline.set_state(Gst.State.PLAYING)
GLib.MainLoop().run()
def is_rtsp_url(url):
return url.startswith('rtsp://')
def main():
# 打印所有传递的命令行参数
#print("脚本名:", sys.argv[0])
#print("参数数量:", len(sys.argv) - 1)
#for i, arg in enumerate(sys.argv[1:], start=1):
# print(f"参数 {i}: {arg}")
if(is_rtsp_url(sys.argv[1])):
fetch_a_frame_from_stream(sys.argv[1])
else:
fetch_a_frame_from_file(sys.argv[1])
if __name__ == "__main__":
main()
2.4 获取真实分辨率的c代码
/**
* 1. make
* 2. ./mpp-dec-h264-to-yuv-file
* 3. gst-launch-1.0 filesrc location=Tennis1080p.yuv ! videoparse width=1920 height=1080 format=nv12 ! videoconvert ! xvimagesink
* 4. gst-launch-1.0 filesrc location=Tennis1080p.h264 ! h264parse ! mppvideodec ! xvimagesink
*/
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <rockchip/rk_mpi.h>
#define __IN_FILE__ ("01.h265")
#define __OUT_FILE__ ("01.yuv")
int dump_frame(MppFrame frame, int *width, int *height, int *h_stride, int *v_stride)
{
//printf("dump_frame_to_file\n");
MppFrameFormat fmt = MPP_FMT_YUV420SP;
MppBuffer buffer = NULL;
RK_U8 *base = NULL;
*width = mpp_frame_get_width(frame);
*height = mpp_frame_get_height(frame);
*h_stride = mpp_frame_get_hor_stride(frame);
*v_stride = mpp_frame_get_ver_stride(frame);
fmt = mpp_frame_get_fmt(frame);
buffer = mpp_frame_get_buffer(frame);
RK_U32 buf_size = mpp_frame_get_buf_size(frame);
//printf("w x h: %dx%d hor_stride:%d ver_stride:%d buf_size:%d\n",width, height, h_stride, v_stride, buf_size);
if (NULL == buffer) {
//printf("buffer is null\n");
return -1;
}
base = (RK_U8 *)mpp_buffer_get_ptr(buffer);
// MPP_FMT_YUV420SP
if (fmt != MPP_FMT_YUV420SP) {
//printf("fmt %d not supported\n", fmt);
return -1;
}
return 0;
}
int main(int argc, char *argv[])
{
//printf("---------- mpp start ----------\n");
if(argc != 2)
{
printf("usage:%s <.h265 file>\n", argv[0]);
printf("return <width>, <height>, <h_stride>, <v_stride>\n");
}
// 1. 打开输入文件
FILE *in_fp = fopen(argv[1], "rb");
if (!in_fp) {
//printf("fopen error\n");
return -1;
}
// 3. 初始化解码器上下文,MppCtx MppApi
MppCtx ctx = NULL;
MppApi *mpi = NULL;
MPP_RET ret = mpp_create(&ctx, &mpi);
if (MPP_OK != ret) {
//printf("mpp_create error\n");
return -1;
}
/**
* 4. 配置解器
* - 解码文件需要 split 模式
* - 设置非阻塞模式,0非阻塞(默认),-1阻塞,+val 超时(ms)
*/
RK_U32 need_split = -1;
ret = mpi->control(ctx, MPP_DEC_SET_PARSER_SPLIT_MODE, (MppParam*)&need_split);
if (MPP_OK != ret) {
//printf("mpi->control error MPP_DEC_SET_PARSER_SPLIT_MODE\n");
return -1;
}
ret = mpp_init(ctx, MPP_CTX_DEC, MPP_VIDEO_CodingHEVC); // 固定为H265 https://blog.csdn.net/weixin_38807927/article/details/135760601
if (MPP_OK != ret) {
//printf("mpp_init error\n");
return -1;
}
// 5. 初始化包,MppPacket
int buf_size = 5 * 1024 * 1024;
char *buf = (char*)malloc(buf_size);
if (!buf) {
//printf("malloc error\n");
return -1;
}
MppPacket pkt = NULL;
ret = mpp_packet_init(&pkt, buf, buf_size);
if (MPP_OK != ret) {
//printf("mpp_packet_init error\n");
return -1;
}
// 6. 循环读取文件,输入解码器,解码,保存结果
int over = 0;
while (!over) {
//printf("decode...\n");
int len = fread(buf, 1, buf_size, in_fp);
//printf("read file length:%d\n", len);
if (0 < len) {
mpp_packet_write(pkt, 0, buf, len);
mpp_packet_set_pos(pkt, buf);
mpp_packet_set_length(pkt, len);
if (feof(in_fp) || len < buf_size) { // 文件读完,设置结束标志位
mpp_packet_set_eos(pkt);
//printf("mpp_packet_set_eos\n");
}
}
/**
* decode_put_packet返回失败,意味着内部缓冲区已满。
* 非阻塞模式,使用pkt_is_send判断当前读取的数据包(buf)是否成功发送。
*/
int pkt_is_send = 0;
while (!pkt_is_send && !over) {
if (0 < len) {
//printf("pkt remain:%d\n", mpp_packet_get_length(pkt));
ret = mpi->decode_put_packet(ctx, pkt);
if (MPP_OK == ret) {
//printf("pkt send success remain:%d\n", mpp_packet_get_length(pkt));
pkt_is_send = 1;
}
}
MppFrame frame;
MPP_RET ret;
ret = mpi->decode_get_frame(ctx, &frame);
if (MPP_OK != ret || !frame) {
//printf("decode_get_frame failed ret:%d\n", ret);
usleep(2000); // 等待一下2ms,通常1080p解码时间2ms
continue;
}
//printf("decode_get_frame success\n");
int width, height, h_stride, v_stride;
dump_frame(frame, &width, &height, &h_stride, &v_stride);
printf("%d, %d, %d, %d\n", width, height, h_stride, v_stride);
exit(0);
if (mpp_frame_get_eos(frame)) {
//printf("mpp_frame_get_eos\n");
mpp_frame_deinit(&frame);
over = 1;
continue;
}
mpp_frame_deinit(&frame);
}
}
// 7. 释放资源
fclose(in_fp);
mpi->reset(ctx);
mpp_packet_deinit(&pkt);
mpp_destroy(ctx);
free(buf);
//printf("---------- mpp over ----------\n");
return 0;
}