parent
80d6953862
commit
29ac3da7b8
79 changed files with 7577 additions and 0 deletions
@ -0,0 +1,21 @@ |
|||||||
|
MIT License |
||||||
|
|
||||||
|
Copyright (c) 2018 comma.ai |
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||||
|
of this software and associated documentation files (the "Software"), to deal |
||||||
|
in the Software without restriction, including without limitation the rights |
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||||
|
copies of the Software, and to permit persons to whom the Software is |
||||||
|
furnished to do so, subject to the following conditions: |
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all |
||||||
|
copies or substantial portions of the Software. |
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||||
|
SOFTWARE. |
@ -0,0 +1,100 @@ |
|||||||
|
#!/usr/bin/env python |
||||||
|
import struct |
||||||
|
from common.numpy_fast import clip |
||||||
|
from common.params import Params |
||||||
|
from copy import copy |
||||||
|
from cereal import car, log |
||||||
|
import cereal.messaging as messaging |
||||||
|
from selfdrive.car.car_helpers import get_car |
||||||
|
from selfdrive.boardd.boardd import can_list_to_can_capnp |
||||||
|
|
||||||
|
HwType = log.HealthData.HwType |
||||||
|
|
||||||
|
|
||||||
|
def steer_thread(): |
||||||
|
poller = messaging.Poller() |
||||||
|
|
||||||
|
logcan = messaging.sub_sock('can') |
||||||
|
health = messaging.sub_sock('health') |
||||||
|
joystick_sock = messaging.sub_sock('testJoystick', conflate=True, poller=poller) |
||||||
|
|
||||||
|
carstate = messaging.pub_sock('carState') |
||||||
|
carcontrol = messaging.pub_sock('carControl') |
||||||
|
sendcan = messaging.pub_sock('sendcan') |
||||||
|
|
||||||
|
button_1_last = 0 |
||||||
|
enabled = False |
||||||
|
|
||||||
|
# wait for health and CAN packets |
||||||
|
hw_type = messaging.recv_one(health).health.hwType |
||||||
|
has_relay = hw_type in [HwType.blackPanda, HwType.uno] |
||||||
|
print("Waiting for CAN messages...") |
||||||
|
messaging.get_one_can(logcan) |
||||||
|
|
||||||
|
CI, CP = get_car(logcan, sendcan, has_relay) |
||||||
|
Params().put("CarParams", CP.to_bytes()) |
||||||
|
|
||||||
|
CC = car.CarControl.new_message() |
||||||
|
|
||||||
|
while True: |
||||||
|
|
||||||
|
# send |
||||||
|
joystick = messaging.recv_one(joystick_sock) |
||||||
|
can_strs = messaging.drain_sock_raw(logcan, wait_for_one=True) |
||||||
|
CS = CI.update(CC, can_strs) |
||||||
|
|
||||||
|
# Usually axis run in pairs, up/down for one, and left/right for |
||||||
|
# the other. |
||||||
|
actuators = car.CarControl.Actuators.new_message() |
||||||
|
|
||||||
|
if joystick is not None: |
||||||
|
axis_3 = clip(-joystick.testJoystick.axes[3] * 1.05, -1., 1.) # -1 to 1 |
||||||
|
actuators.steer = axis_3 |
||||||
|
actuators.steerAngle = axis_3 * 43. # deg |
||||||
|
axis_1 = clip(-joystick.testJoystick.axes[1] * 1.05, -1., 1.) # -1 to 1 |
||||||
|
actuators.gas = max(axis_1, 0.) |
||||||
|
actuators.brake = max(-axis_1, 0.) |
||||||
|
|
||||||
|
pcm_cancel_cmd = joystick.testJoystick.buttons[0] |
||||||
|
button_1 = joystick.testJoystick.buttons[1] |
||||||
|
if button_1 and not button_1_last: |
||||||
|
enabled = not enabled |
||||||
|
|
||||||
|
button_1_last = button_1 |
||||||
|
|
||||||
|
#print "enable", enabled, "steer", actuators.steer, "accel", actuators.gas - actuators.brake |
||||||
|
|
||||||
|
hud_alert = 0 |
||||||
|
audible_alert = 0 |
||||||
|
if joystick.testJoystick.buttons[2]: |
||||||
|
audible_alert = "beepSingle" |
||||||
|
if joystick.testJoystick.buttons[3]: |
||||||
|
audible_alert = "chimeRepeated" |
||||||
|
hud_alert = "steerRequired" |
||||||
|
|
||||||
|
CC.actuators.gas = actuators.gas |
||||||
|
CC.actuators.brake = actuators.brake |
||||||
|
CC.actuators.steer = actuators.steer |
||||||
|
CC.actuators.steerAngle = actuators.steerAngle |
||||||
|
CC.hudControl.visualAlert = hud_alert |
||||||
|
CC.hudControl.setSpeed = 20 |
||||||
|
CC.cruiseControl.cancel = pcm_cancel_cmd |
||||||
|
CC.enabled = enabled |
||||||
|
can_sends = CI.apply(CC) |
||||||
|
sendcan.send(can_list_to_can_capnp(can_sends, msgtype='sendcan')) |
||||||
|
|
||||||
|
# broadcast carState |
||||||
|
cs_send = messaging.new_message() |
||||||
|
cs_send.init('carState') |
||||||
|
cs_send.carState = copy(CS) |
||||||
|
carstate.send(cs_send.to_bytes()) |
||||||
|
|
||||||
|
# broadcast carControl |
||||||
|
cc_send = messaging.new_message() |
||||||
|
cc_send.init('carControl') |
||||||
|
cc_send.carControl = copy(CC) |
||||||
|
carcontrol.send(cc_send.to_bytes()) |
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__": |
||||||
|
steer_thread() |
@ -0,0 +1,125 @@ |
|||||||
|
#!/usr/bin/env python |
||||||
|
import pygame |
||||||
|
|
||||||
|
# Define some colors |
||||||
|
BLACK = ( 0, 0, 0) |
||||||
|
WHITE = ( 255, 255, 255) |
||||||
|
|
||||||
|
# This is a simple class that will help us print to the screen |
||||||
|
# It has nothing to do with the joysticks, just outputting the |
||||||
|
# information. |
||||||
|
class TextPrint: |
||||||
|
def __init__(self): |
||||||
|
self.reset() |
||||||
|
self.font = pygame.font.Font(None, 20) |
||||||
|
|
||||||
|
def printf(self, screen, textString): |
||||||
|
textBitmap = self.font.render(textString, True, BLACK) |
||||||
|
screen.blit(textBitmap, [self.x, self.y]) |
||||||
|
self.y += self.line_height |
||||||
|
|
||||||
|
def reset(self): |
||||||
|
self.x = 10 |
||||||
|
self.y = 10 |
||||||
|
self.line_height = 15 |
||||||
|
|
||||||
|
def indent(self): |
||||||
|
self.x += 10 |
||||||
|
|
||||||
|
def unindent(self): |
||||||
|
self.x -= 10 |
||||||
|
|
||||||
|
|
||||||
|
pygame.init() |
||||||
|
|
||||||
|
# Set the width and height of the screen [width,height] |
||||||
|
size = [500, 700] |
||||||
|
screen = pygame.display.set_mode(size) |
||||||
|
|
||||||
|
pygame.display.set_caption("My Game") |
||||||
|
|
||||||
|
#Loop until the user clicks the close button. |
||||||
|
done = False |
||||||
|
|
||||||
|
# Used to manage how fast the screen updates |
||||||
|
clock = pygame.time.Clock() |
||||||
|
|
||||||
|
# Initialize the joysticks |
||||||
|
pygame.joystick.init() |
||||||
|
|
||||||
|
# Get ready to print |
||||||
|
textPrint = TextPrint() |
||||||
|
|
||||||
|
# -------- Main Program Loop ----------- |
||||||
|
while done==False: |
||||||
|
# EVENT PROCESSING STEP |
||||||
|
for event in pygame.event.get(): # User did something |
||||||
|
if event.type == pygame.QUIT: # If user clicked close |
||||||
|
done=True # Flag that we are done so we exit this loop |
||||||
|
|
||||||
|
# Possible joystick actions: JOYAXISMOTION JOYBALLMOTION JOYBUTTONDOWN JOYBUTTONUP JOYHATMOTION |
||||||
|
if event.type == pygame.JOYBUTTONDOWN: |
||||||
|
print("Joystick button pressed.") |
||||||
|
if event.type == pygame.JOYBUTTONUP: |
||||||
|
print("Joystick button released.") |
||||||
|
|
||||||
|
|
||||||
|
# DRAWING STEP |
||||||
|
# First, clear the screen to white. Don't put other drawing commands |
||||||
|
# above this, or they will be erased with this command. |
||||||
|
screen.fill(WHITE) |
||||||
|
textPrint.reset() |
||||||
|
|
||||||
|
# Get count of joysticks |
||||||
|
joystick_count = pygame.joystick.get_count() |
||||||
|
|
||||||
|
textPrint.printf(screen, "Number of joysticks: {}".format(joystick_count) ) |
||||||
|
textPrint.indent() |
||||||
|
|
||||||
|
# For each joystick: |
||||||
|
joystick = pygame.joystick.Joystick(0) |
||||||
|
joystick.init() |
||||||
|
|
||||||
|
textPrint.printf(screen, "Joystick {}".format(0) ) |
||||||
|
textPrint.indent() |
||||||
|
|
||||||
|
# Get the name from the OS for the controller/joystick |
||||||
|
name = joystick.get_name() |
||||||
|
textPrint.printf(screen, "Joystick name: {}".format(name) ) |
||||||
|
|
||||||
|
# Usually axis run in pairs, up/down for one, and left/right for |
||||||
|
# the other. |
||||||
|
axes = joystick.get_numaxes() |
||||||
|
textPrint.printf(screen, "Number of axes: {}".format(axes) ) |
||||||
|
textPrint.indent() |
||||||
|
|
||||||
|
for i in range( axes ): |
||||||
|
axis = joystick.get_axis( i ) |
||||||
|
textPrint.printf(screen, "Axis {} value: {:>6.3f}".format(i, axis) ) |
||||||
|
textPrint.unindent() |
||||||
|
|
||||||
|
buttons = joystick.get_numbuttons() |
||||||
|
textPrint.printf(screen, "Number of buttons: {}".format(buttons) ) |
||||||
|
textPrint.indent() |
||||||
|
|
||||||
|
for i in range( buttons ): |
||||||
|
button = joystick.get_button( i ) |
||||||
|
textPrint.printf(screen, "Button {:>2} value: {}".format(i,button) ) |
||||||
|
textPrint.unindent() |
||||||
|
|
||||||
|
|
||||||
|
textPrint.unindent() |
||||||
|
|
||||||
|
|
||||||
|
# ALL CODE TO DRAW SHOULD GO ABOVE THIS COMMENT |
||||||
|
|
||||||
|
# Go ahead and update the screen with what we've drawn. |
||||||
|
pygame.display.flip() |
||||||
|
|
||||||
|
# Limit to 20 frames per second |
||||||
|
clock.tick(20) |
||||||
|
|
||||||
|
# Close the window and quit. |
||||||
|
# If you forget this line, the program will 'hang' |
||||||
|
# on exit if running from IDLE. |
||||||
|
pygame.quit () |
@ -0,0 +1,68 @@ |
|||||||
|
#!/usr/bin/env python |
||||||
|
|
||||||
|
# This process publishes joystick events. Such events can be suscribed by |
||||||
|
# mocked car controller scripts. |
||||||
|
|
||||||
|
|
||||||
|
### this process needs pygame and can't run on the EON ### |
||||||
|
|
||||||
|
import pygame |
||||||
|
import zmq |
||||||
|
import cereal.messaging as messaging |
||||||
|
|
||||||
|
|
||||||
|
def joystick_thread(): |
||||||
|
joystick_sock = messaging.pub_sock('testJoystick') |
||||||
|
|
||||||
|
pygame.init() |
||||||
|
|
||||||
|
# Used to manage how fast the screen updates |
||||||
|
clock = pygame.time.Clock() |
||||||
|
|
||||||
|
# Initialize the joysticks |
||||||
|
pygame.joystick.init() |
||||||
|
|
||||||
|
# Get count of joysticks |
||||||
|
joystick_count = pygame.joystick.get_count() |
||||||
|
if joystick_count > 1: |
||||||
|
raise ValueError("More than one joystick attached") |
||||||
|
elif joystick_count < 1: |
||||||
|
raise ValueError("No joystick found") |
||||||
|
|
||||||
|
# -------- Main Program Loop ----------- |
||||||
|
while True: |
||||||
|
# EVENT PROCESSING STEP |
||||||
|
for event in pygame.event.get(): # User did something |
||||||
|
if event.type == pygame.QUIT: # If user clicked close |
||||||
|
pass |
||||||
|
# Available joystick events: JOYAXISMOTION JOYBALLMOTION JOYBUTTONDOWN JOYBUTTONUP JOYHATMOTION |
||||||
|
if event.type == pygame.JOYBUTTONDOWN: |
||||||
|
print("Joystick button pressed.") |
||||||
|
if event.type == pygame.JOYBUTTONUP: |
||||||
|
print("Joystick button released.") |
||||||
|
|
||||||
|
joystick = pygame.joystick.Joystick(0) |
||||||
|
joystick.init() |
||||||
|
|
||||||
|
# Usually axis run in pairs, up/down for one, and left/right for |
||||||
|
# the other. |
||||||
|
axes = [] |
||||||
|
buttons = [] |
||||||
|
|
||||||
|
for a in range(joystick.get_numaxes()): |
||||||
|
axes.append(joystick.get_axis(a)) |
||||||
|
|
||||||
|
for b in range(joystick.get_numbuttons()): |
||||||
|
buttons.append(bool(joystick.get_button(b))) |
||||||
|
|
||||||
|
dat = messaging.new_message() |
||||||
|
dat.init('testJoystick') |
||||||
|
dat.testJoystick.axes = axes |
||||||
|
dat.testJoystick.buttons = buttons |
||||||
|
joystick_sock.send(dat.to_bytes()) |
||||||
|
|
||||||
|
# Limit to 100 frames per second |
||||||
|
clock.tick(100) |
||||||
|
|
||||||
|
if __name__ == "__main__": |
||||||
|
joystick_thread() |
@ -0,0 +1 @@ |
|||||||
|
cframereader.cpp |
@ -0,0 +1,176 @@ |
|||||||
|
#include "FrameReader.hpp" |
||||||
|
#include <assert.h> |
||||||
|
#include <unistd.h> |
||||||
|
|
||||||
|
static int ffmpeg_lockmgr_cb(void **arg, enum AVLockOp op) { |
||||||
|
pthread_mutex_t *mutex = (pthread_mutex_t *)*arg; |
||||||
|
int err; |
||||||
|
|
||||||
|
switch (op) { |
||||||
|
case AV_LOCK_CREATE: |
||||||
|
mutex = (pthread_mutex_t *)malloc(sizeof(*mutex)); |
||||||
|
if (!mutex) |
||||||
|
return AVERROR(ENOMEM); |
||||||
|
if ((err = pthread_mutex_init(mutex, NULL))) { |
||||||
|
free(mutex); |
||||||
|
return AVERROR(err); |
||||||
|
} |
||||||
|
*arg = mutex; |
||||||
|
return 0; |
||||||
|
case AV_LOCK_OBTAIN: |
||||||
|
if ((err = pthread_mutex_lock(mutex))) |
||||||
|
return AVERROR(err); |
||||||
|
|
||||||
|
return 0; |
||||||
|
case AV_LOCK_RELEASE: |
||||||
|
if ((err = pthread_mutex_unlock(mutex))) |
||||||
|
return AVERROR(err); |
||||||
|
|
||||||
|
return 0; |
||||||
|
case AV_LOCK_DESTROY: |
||||||
|
if (mutex) |
||||||
|
pthread_mutex_destroy(mutex); |
||||||
|
free(mutex); |
||||||
|
*arg = NULL; |
||||||
|
return 0; |
||||||
|
} |
||||||
|
return 1; |
||||||
|
} |
||||||
|
|
||||||
|
FrameReader::FrameReader(const char *fn) { |
||||||
|
int ret; |
||||||
|
|
||||||
|
ret = av_lockmgr_register(ffmpeg_lockmgr_cb); |
||||||
|
assert(ret >= 0); |
||||||
|
|
||||||
|
avformat_network_init(); |
||||||
|
av_register_all(); |
||||||
|
|
||||||
|
snprintf(url, sizeof(url)-1, "http://data.comma.life/%s", fn); |
||||||
|
t = new std::thread([&]() { this->loaderThread(); }); |
||||||
|
} |
||||||
|
|
||||||
|
void FrameReader::loaderThread() { |
||||||
|
int ret; |
||||||
|
|
||||||
|
if (avformat_open_input(&pFormatCtx, url, NULL, NULL) != 0) { |
||||||
|
fprintf(stderr, "error loading %s\n", url); |
||||||
|
valid = false; |
||||||
|
return; |
||||||
|
} |
||||||
|
av_dump_format(pFormatCtx, 0, url, 0); |
||||||
|
|
||||||
|
auto pCodecCtxOrig = pFormatCtx->streams[0]->codec; |
||||||
|
auto pCodec = avcodec_find_decoder(pCodecCtxOrig->codec_id); |
||||||
|
assert(pCodec != NULL); |
||||||
|
|
||||||
|
pCodecCtx = avcodec_alloc_context3(pCodec); |
||||||
|
ret = avcodec_copy_context(pCodecCtx, pCodecCtxOrig); |
||||||
|
assert(ret == 0); |
||||||
|
|
||||||
|
ret = avcodec_open2(pCodecCtx, pCodec, NULL); |
||||||
|
assert(ret >= 0); |
||||||
|
|
||||||
|
sws_ctx = sws_getContext(width, height, AV_PIX_FMT_YUV420P, |
||||||
|
width, height, AV_PIX_FMT_BGR24, |
||||||
|
SWS_BILINEAR, NULL, NULL, NULL); |
||||||
|
assert(sws_ctx != NULL); |
||||||
|
|
||||||
|
AVPacket *pkt = (AVPacket *)malloc(sizeof(AVPacket)); |
||||||
|
assert(pkt != NULL); |
||||||
|
bool first = true; |
||||||
|
while (av_read_frame(pFormatCtx, pkt)>=0) { |
||||||
|
//printf("%d pkt %d %d\n", pkts.size(), pkt->size, pkt->pos);
|
||||||
|
if (first) { |
||||||
|
AVFrame *pFrame = av_frame_alloc(); |
||||||
|
int frameFinished; |
||||||
|
avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, pkt); |
||||||
|
first = false; |
||||||
|
} |
||||||
|
pkts.push_back(pkt); |
||||||
|
pkt = (AVPacket *)malloc(sizeof(AVPacket)); |
||||||
|
assert(pkt != NULL); |
||||||
|
} |
||||||
|
free(pkt); |
||||||
|
printf("framereader download done\n"); |
||||||
|
joined = true; |
||||||
|
|
||||||
|
// cache
|
||||||
|
while (1) { |
||||||
|
GOPCache(to_cache.get()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
void FrameReader::GOPCache(int idx) { |
||||||
|
AVFrame *pFrame; |
||||||
|
int gop = idx - idx%15; |
||||||
|
|
||||||
|
mcache.lock(); |
||||||
|
bool has_gop = cache.find(gop) != cache.end(); |
||||||
|
mcache.unlock(); |
||||||
|
|
||||||
|
if (!has_gop) { |
||||||
|
//printf("caching %d\n", gop);
|
||||||
|
for (int i = gop; i < gop+15; i++) { |
||||||
|
if (i >= pkts.size()) break; |
||||||
|
//printf("decode %d\n", i);
|
||||||
|
int frameFinished; |
||||||
|
pFrame = av_frame_alloc(); |
||||||
|
avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, pkts[i]); |
||||||
|
uint8_t *dat = toRGB(pFrame)->data[0]; |
||||||
|
mcache.lock(); |
||||||
|
cache.insert(std::make_pair(i, dat)); |
||||||
|
mcache.unlock(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
AVFrame *FrameReader::toRGB(AVFrame *pFrame) { |
||||||
|
AVFrame *pFrameRGB = av_frame_alloc(); |
||||||
|
int numBytes = avpicture_get_size(AV_PIX_FMT_BGR24, pFrame->width, pFrame->height); |
||||||
|
uint8_t *buffer = (uint8_t *)av_malloc(numBytes*sizeof(uint8_t)); |
||||||
|
avpicture_fill((AVPicture *)pFrameRGB, buffer, AV_PIX_FMT_BGR24, pFrame->width, pFrame->height); |
||||||
|
sws_scale(sws_ctx, (uint8_t const * const *)pFrame->data, |
||||||
|
pFrame->linesize, 0, pFrame->height, |
||||||
|
pFrameRGB->data, pFrameRGB->linesize); |
||||||
|
return pFrameRGB; |
||||||
|
} |
||||||
|
|
||||||
|
uint8_t *FrameReader::get(int idx) { |
||||||
|
if (!valid) return NULL; |
||||||
|
waitForReady(); |
||||||
|
// TODO: one line?
|
||||||
|
uint8_t *dat = NULL; |
||||||
|
|
||||||
|
// lookahead
|
||||||
|
to_cache.put(idx); |
||||||
|
to_cache.put(idx+15); |
||||||
|
|
||||||
|
mcache.lock(); |
||||||
|
auto it = cache.find(idx); |
||||||
|
if (it != cache.end()) { |
||||||
|
dat = it->second; |
||||||
|
} |
||||||
|
mcache.unlock(); |
||||||
|
|
||||||
|
if (dat == NULL) { |
||||||
|
to_cache.put_front(idx); |
||||||
|
// lookahead
|
||||||
|
while (dat == NULL) { |
||||||
|
// wait for frame
|
||||||
|
usleep(50*1000); |
||||||
|
// check for frame
|
||||||
|
mcache.lock(); |
||||||
|
auto it = cache.find(idx); |
||||||
|
if (it != cache.end()) dat = it->second; |
||||||
|
mcache.unlock(); |
||||||
|
if (dat == NULL) { |
||||||
|
printf("."); |
||||||
|
fflush(stdout); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return dat; |
||||||
|
} |
||||||
|
|
@ -0,0 +1,58 @@ |
|||||||
|
#ifndef FRAMEREADER_HPP |
||||||
|
#define FRAMEREADER_HPP |
||||||
|
|
||||||
|
#include <unistd.h> |
||||||
|
#include <vector> |
||||||
|
#include <map> |
||||||
|
#include <thread> |
||||||
|
#include <mutex> |
||||||
|
#include <list> |
||||||
|
#include <condition_variable> |
||||||
|
|
||||||
|
#include "channel.hpp" |
||||||
|
|
||||||
|
// independent of QT, needs ffmpeg
|
||||||
|
extern "C" { |
||||||
|
#include <libavcodec/avcodec.h> |
||||||
|
#include <libavformat/avformat.h> |
||||||
|
#include <libswscale/swscale.h> |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
class FrameReader { |
||||||
|
public: |
||||||
|
FrameReader(const char *fn); |
||||||
|
uint8_t *get(int idx); |
||||||
|
AVFrame *toRGB(AVFrame *); |
||||||
|
void waitForReady() { |
||||||
|
while (!joined) usleep(10*1000); |
||||||
|
} |
||||||
|
int getRGBSize() { return width*height*3; } |
||||||
|
void loaderThread(); |
||||||
|
void cacherThread(); |
||||||
|
private: |
||||||
|
AVFormatContext *pFormatCtx = NULL; |
||||||
|
AVCodecContext *pCodecCtx = NULL; |
||||||
|
|
||||||
|
struct SwsContext *sws_ctx = NULL; |
||||||
|
|
||||||
|
int width = 1164; |
||||||
|
int height = 874; |
||||||
|
|
||||||
|
std::vector<AVPacket *> pkts; |
||||||
|
|
||||||
|
std::thread *t; |
||||||
|
bool joined = false; |
||||||
|
|
||||||
|
std::map<int, uint8_t *> cache; |
||||||
|
std::mutex mcache; |
||||||
|
|
||||||
|
void GOPCache(int idx); |
||||||
|
channel<int> to_cache; |
||||||
|
|
||||||
|
bool valid = true; |
||||||
|
char url[0x400]; |
||||||
|
}; |
||||||
|
|
||||||
|
#endif |
||||||
|
|
@ -0,0 +1,8 @@ |
|||||||
|
Import('env') |
||||||
|
from sysconfig import get_paths |
||||||
|
env['CPPPATH'] += [get_paths()['include']] |
||||||
|
|
||||||
|
from Cython.Build import cythonize |
||||||
|
cythonize("cframereader.pyx") |
||||||
|
env.SharedLibrary(File('cframereader.so'), ['cframereader.cpp', 'FrameReader.cpp'], LIBS=['avformat', 'avcodec', 'avutil', 'swscale']) |
||||||
|
|
@ -0,0 +1,20 @@ |
|||||||
|
# distutils: language = c++ |
||||||
|
# cython: language_level=3 |
||||||
|
|
||||||
|
cdef extern from "FrameReader.hpp": |
||||||
|
cdef cppclass CFrameReader "FrameReader": |
||||||
|
CFrameReader(const char *) |
||||||
|
char *get(int) |
||||||
|
|
||||||
|
cdef class FrameReader(): |
||||||
|
cdef CFrameReader *fr |
||||||
|
|
||||||
|
def __cinit__(self, fn): |
||||||
|
self.fr = new CFrameReader(fn) |
||||||
|
|
||||||
|
def __dealloc__(self): |
||||||
|
del self.fr |
||||||
|
|
||||||
|
def get(self, idx): |
||||||
|
self.fr.get(idx) |
||||||
|
|
@ -0,0 +1,35 @@ |
|||||||
|
#ifndef CHANNEL_HPP |
||||||
|
#define CHANNEL_HPP |
||||||
|
|
||||||
|
#include <mutex> |
||||||
|
#include <list> |
||||||
|
#include <condition_variable> |
||||||
|
|
||||||
|
template<class item> |
||||||
|
class channel { |
||||||
|
private: |
||||||
|
std::list<item> queue; |
||||||
|
std::mutex m; |
||||||
|
std::condition_variable cv; |
||||||
|
public: |
||||||
|
void put(const item &i) { |
||||||
|
std::unique_lock<std::mutex> lock(m); |
||||||
|
queue.push_back(i); |
||||||
|
cv.notify_one(); |
||||||
|
} |
||||||
|
void put_front(const item &i) { |
||||||
|
std::unique_lock<std::mutex> lock(m); |
||||||
|
queue.push_front(i); |
||||||
|
cv.notify_one(); |
||||||
|
} |
||||||
|
item get() { |
||||||
|
std::unique_lock<std::mutex> lock(m); |
||||||
|
cv.wait(lock, [&](){ return !queue.empty(); }); |
||||||
|
item result = queue.front(); |
||||||
|
queue.pop_front(); |
||||||
|
return result; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
#endif |
||||||
|
|
@ -0,0 +1,352 @@ |
|||||||
|
import functools |
||||||
|
import threading |
||||||
|
import inspect |
||||||
|
import sys |
||||||
|
import select |
||||||
|
import struct |
||||||
|
from math import sqrt |
||||||
|
from collections import OrderedDict, deque |
||||||
|
from time import time |
||||||
|
|
||||||
|
from tools.lib.pollable_queue import PollableQueue, Empty, Full, ExistentialError |
||||||
|
|
||||||
|
EndSentinel = object() |
||||||
|
|
||||||
|
|
||||||
|
def _sync_inner_generator(input_queue, *args, **kwargs): |
||||||
|
func = args[0] |
||||||
|
args = args[1:] |
||||||
|
|
||||||
|
get = input_queue.get |
||||||
|
while True: |
||||||
|
item = get() |
||||||
|
if item is EndSentinel: |
||||||
|
return |
||||||
|
|
||||||
|
cookie, value = item |
||||||
|
yield cookie, func(value, *args, **kwargs) |
||||||
|
|
||||||
|
|
||||||
|
def _async_streamer_async_inner(input_queue, output_queue, generator_func, args, kwargs): |
||||||
|
put = output_queue.put |
||||||
|
put_end = True |
||||||
|
try: |
||||||
|
g = generator_func(input_queue, *args, **kwargs) |
||||||
|
for item in g: |
||||||
|
put((time(), item)) |
||||||
|
g.close() |
||||||
|
except ExistentialError: |
||||||
|
put_end = False |
||||||
|
raise |
||||||
|
finally: |
||||||
|
if put_end: |
||||||
|
put((None, EndSentinel)) |
||||||
|
|
||||||
|
def _running_mean_var(ltc_stats, x): |
||||||
|
old_mean, var = ltc_stats |
||||||
|
mean = min(600., 0.98 * old_mean + 0.02 * x) |
||||||
|
var = min(5., max(0.1, 0.98 * var + 0.02 * (mean - x) * (old_mean - x))) |
||||||
|
return mean, var |
||||||
|
|
||||||
|
def _find_next_resend(sent_messages, ltc_stats): |
||||||
|
if not sent_messages: |
||||||
|
return None, None |
||||||
|
|
||||||
|
oldest_sent_idx = sent_messages._OrderedDict__root[1][2] |
||||||
|
send_time, _ = sent_messages[oldest_sent_idx] |
||||||
|
|
||||||
|
# Assume message has been lost if it is >10 standard deviations from mean. |
||||||
|
mean, var = ltc_stats |
||||||
|
next_resend_time = send_time + mean + 40. * sqrt(var) |
||||||
|
|
||||||
|
return oldest_sent_idx, next_resend_time |
||||||
|
|
||||||
|
|
||||||
|
def _do_cleanup(input_queue, output_queue, num_workers, sentinels_received, num_outstanding): |
||||||
|
input_fd = input_queue.put_fd() |
||||||
|
output_fd = output_queue.get_fd() |
||||||
|
|
||||||
|
poller = select.epoll() |
||||||
|
poller.register(input_fd, select.EPOLLOUT) |
||||||
|
poller.register(output_fd, select.EPOLLIN) |
||||||
|
|
||||||
|
remaining_outputs = [] |
||||||
|
end_sentinels_to_send = num_workers - sentinels_received |
||||||
|
while sentinels_received < num_workers: |
||||||
|
evts = dict(poller.poll(-1 if num_outstanding > 0 else 10.)) |
||||||
|
if not evts: |
||||||
|
# Workers aren't responding, crash. |
||||||
|
break |
||||||
|
|
||||||
|
if output_fd in evts: |
||||||
|
_, maybe_sentinel = output_queue.get() |
||||||
|
if maybe_sentinel is EndSentinel: |
||||||
|
sentinels_received += 1 |
||||||
|
else: |
||||||
|
remaining_outputs.append(maybe_sentinel[1]) |
||||||
|
num_outstanding -= 1 |
||||||
|
|
||||||
|
if input_fd in evts: |
||||||
|
if end_sentinels_to_send > 0: |
||||||
|
input_queue.put_nowait(EndSentinel) |
||||||
|
end_sentinels_to_send -= 1 |
||||||
|
else: |
||||||
|
poller.modify(input_fd, 0) |
||||||
|
|
||||||
|
# TODO: Raise an exception when a queue thread raises one. |
||||||
|
assert sentinels_received == num_workers, (sentinels_received, num_workers) |
||||||
|
assert output_queue.empty() |
||||||
|
return remaining_outputs |
||||||
|
|
||||||
|
def _generate_results(input_stream, input_queue, worker_output_queue, output_queue, |
||||||
|
num_workers, max_outstanding): |
||||||
|
pack_cookie = struct.pack |
||||||
|
|
||||||
|
# Maps idx -> (send_time, input) |
||||||
|
sent_messages = OrderedDict() |
||||||
|
oldest_sent_idx = None |
||||||
|
next_resend_time = None |
||||||
|
ltc_stats = 5., 10. |
||||||
|
|
||||||
|
# Maps idx -> result |
||||||
|
received_messages = {} |
||||||
|
next_out = 0 |
||||||
|
|
||||||
|
# Start things off by pulling the first value. |
||||||
|
next_in_item = next(input_stream, EndSentinel) |
||||||
|
inputs_remain = next_in_item is not EndSentinel |
||||||
|
sentinels_received = 0 |
||||||
|
|
||||||
|
input_fd = input_queue.put_fd() |
||||||
|
worker_output_fd = worker_output_queue.get_fd() |
||||||
|
output_fd = output_queue.put_fd() |
||||||
|
|
||||||
|
poller = select.epoll() |
||||||
|
poller.register(input_fd, select.EPOLLOUT) |
||||||
|
poller.register(worker_output_fd, select.EPOLLIN) |
||||||
|
poller.register(output_fd, 0) |
||||||
|
|
||||||
|
# Keep sending/retrying until the input stream and sent messages are all done. |
||||||
|
while sentinels_received < num_workers and (inputs_remain or sent_messages): |
||||||
|
if max_outstanding: |
||||||
|
can_send_new = (len(sent_messages) < max_outstanding and |
||||||
|
len(received_messages) < max_outstanding and inputs_remain) |
||||||
|
else: |
||||||
|
can_send_new = inputs_remain |
||||||
|
|
||||||
|
if (next_resend_time and now >= next_resend_time) or can_send_new: |
||||||
|
poller.modify(input_fd, select.EPOLLOUT) |
||||||
|
else: |
||||||
|
poller.modify(input_fd, 0) |
||||||
|
|
||||||
|
if next_resend_time: |
||||||
|
t = max(0, next_resend_time - now) |
||||||
|
evts = dict(poller.poll(t)) |
||||||
|
else: |
||||||
|
evts = dict(poller.poll()) |
||||||
|
now = time() |
||||||
|
|
||||||
|
if output_fd in evts: |
||||||
|
output_queue.put_nowait(received_messages.pop(next_out)) |
||||||
|
next_out += 1 |
||||||
|
|
||||||
|
if next_out not in received_messages: |
||||||
|
poller.modify(output_fd, 0) |
||||||
|
|
||||||
|
if worker_output_fd in evts: |
||||||
|
for receive_time, maybe_sentinel in worker_output_queue.get_multiple_nowait(): |
||||||
|
# Check for EndSentinel in case of worker crash. |
||||||
|
if maybe_sentinel is EndSentinel: |
||||||
|
sentinels_received += 1 |
||||||
|
continue |
||||||
|
idx_bytes, value = maybe_sentinel |
||||||
|
idx = struct.unpack("<Q", idx_bytes)[0] |
||||||
|
|
||||||
|
# Don't store duplicates. |
||||||
|
sent_message = sent_messages.pop(idx, None) |
||||||
|
if sent_message is not None: |
||||||
|
received_messages[idx] = value |
||||||
|
ltc_stats = _running_mean_var(ltc_stats, receive_time - sent_message[0]) |
||||||
|
if idx == oldest_sent_idx: |
||||||
|
oldest_sent_idx, next_resend_time = _find_next_resend(sent_messages, ltc_stats) |
||||||
|
|
||||||
|
if idx == next_out: |
||||||
|
poller.modify(output_fd, select.EPOLLOUT) |
||||||
|
else: |
||||||
|
if oldest_sent_idx is not None: |
||||||
|
# The message was resent, so it is at least old as the oldest tracked message. |
||||||
|
ltc_stats = _running_mean_var(ltc_stats, now - sent_messages[oldest_sent_idx][0]) |
||||||
|
elif input_fd in evts: |
||||||
|
if can_send_new: |
||||||
|
# We're under the limit, get the next input. |
||||||
|
send_idx, send_value = next_in_item |
||||||
|
input_queue.put_nowait((pack_cookie("<Q", send_idx), send_value)) |
||||||
|
sent_messages[next_in_item[0]] = now, next_in_item[1] |
||||||
|
next_in_item = next(input_stream, EndSentinel) |
||||||
|
inputs_remain = next_in_item is not EndSentinel |
||||||
|
|
||||||
|
if oldest_sent_idx is None: |
||||||
|
oldest_sent_idx, next_resend_time = _find_next_resend(sent_messages, ltc_stats) |
||||||
|
else: |
||||||
|
# Move the resent item to the end. |
||||||
|
send_time, resend_input = sent_messages.pop(oldest_sent_idx) |
||||||
|
|
||||||
|
sys.stdout.write("Resending {} (ltc, mean, var) = ({}, {}, {})\n".format( |
||||||
|
oldest_sent_idx, now - send_time, ltc_stats[0], ltc_stats[1])) |
||||||
|
input_queue.put_nowait((pack_cookie("<Q", oldest_sent_idx), resend_input)) |
||||||
|
|
||||||
|
sent_messages[oldest_sent_idx] = now, resend_input |
||||||
|
oldest_sent_idx, next_resend_time = _find_next_resend(sent_messages, ltc_stats) |
||||||
|
|
||||||
|
# Return remaining messages. |
||||||
|
while next_out in received_messages: |
||||||
|
output_queue.put(received_messages.pop(next_out)) |
||||||
|
next_out += 1 |
||||||
|
|
||||||
|
_do_cleanup(input_queue, worker_output_queue, num_workers, sentinels_received, 0) |
||||||
|
output_queue.put(EndSentinel) |
||||||
|
|
||||||
|
|
||||||
|
def _generate_results_unreliable(input_stream, input_queue, worker_output_queue, |
||||||
|
output_queue, num_workers, max_outstanding_unused): |
||||||
|
# Start things off by pulling the first value. |
||||||
|
next_in_item = next(input_stream, EndSentinel) |
||||||
|
inputs_remain = next_in_item is not EndSentinel |
||||||
|
|
||||||
|
# TODO: Use heapq to return oldest message. |
||||||
|
received_messages = deque() |
||||||
|
pack_cookie = struct.pack |
||||||
|
|
||||||
|
input_fd = input_queue.put_fd() |
||||||
|
worker_output_fd = worker_output_queue.get_fd() |
||||||
|
output_fd = output_queue.put_fd() |
||||||
|
|
||||||
|
poller = select.epoll() |
||||||
|
poller.register(input_fd, select.EPOLLOUT) |
||||||
|
poller.register(worker_output_fd, select.EPOLLIN) |
||||||
|
poller.register(output_fd, 0) |
||||||
|
|
||||||
|
# Keep sending/retrying until the input stream and sent messages are all done. |
||||||
|
num_outstanding = 0 |
||||||
|
sentinels_received = 0 |
||||||
|
while sentinels_received < num_workers and (inputs_remain or received_messages): |
||||||
|
# Avoid poll() if we can easily detect that there is work to do. |
||||||
|
evts = (input_fd if inputs_remain and not input_queue.full() else 0, output_fd |
||||||
|
if not output_queue.full() and len(received_messages) else 0, worker_output_fd |
||||||
|
if not worker_output_queue.empty() else 0) |
||||||
|
if all(evt == 0 for evt in evts): |
||||||
|
evts = dict(poller.poll()) |
||||||
|
|
||||||
|
if output_fd in evts: |
||||||
|
output_queue.put(received_messages.pop()) |
||||||
|
|
||||||
|
if len(received_messages) == 0: |
||||||
|
poller.modify(output_fd, 0) |
||||||
|
|
||||||
|
if worker_output_fd in evts: |
||||||
|
for receive_time, maybe_sentinel in worker_output_queue.get_multiple(): |
||||||
|
# Check for EndSentinel in case of worker crash. |
||||||
|
if maybe_sentinel is EndSentinel: |
||||||
|
sentinels_received += 1 |
||||||
|
continue |
||||||
|
received_messages.appendleft(maybe_sentinel[1]) |
||||||
|
num_outstanding -= 1 |
||||||
|
poller.modify(output_fd, select.EPOLLOUT) |
||||||
|
|
||||||
|
if input_fd in evts: |
||||||
|
# We're under the limit, get the next input. |
||||||
|
send_idx, send_value = next_in_item |
||||||
|
input_queue.put((pack_cookie("<Q", send_idx), send_value)) |
||||||
|
next_in_item = next(input_stream, EndSentinel) |
||||||
|
inputs_remain = next_in_item is not EndSentinel |
||||||
|
num_outstanding += 1 |
||||||
|
|
||||||
|
if not inputs_remain: |
||||||
|
poller.modify(input_fd, 0) |
||||||
|
|
||||||
|
# TODO: Track latency even though we don't retry. |
||||||
|
for value in _do_cleanup(input_queue, worker_output_queue, num_workers, |
||||||
|
sentinels_received, num_outstanding): |
||||||
|
output_queue.put(value) |
||||||
|
output_queue.put(EndSentinel) |
||||||
|
|
||||||
|
|
||||||
|
def _async_generator(func, max_workers, in_q_size, out_q_size, max_outstanding, |
||||||
|
async_inner, reliable): |
||||||
|
if async_inner: |
||||||
|
assert inspect.isgeneratorfunction( |
||||||
|
func), "async_inner == True but {} is not a generator".format(func) |
||||||
|
|
||||||
|
@functools.wraps(func) |
||||||
|
def wrapper(input_sequence_or_self, *args, **kwargs): |
||||||
|
# HACK: Determine whether the first arg is "self". ismethod returns False here. |
||||||
|
if inspect.getargspec(func).args[0] == "self": |
||||||
|
inner_func = func.__get__(input_sequence_or_self, type(input_sequence_or_self)) |
||||||
|
input_sequence = args[0] |
||||||
|
args = args[1:] |
||||||
|
else: |
||||||
|
inner_func = func |
||||||
|
input_sequence = input_sequence_or_self |
||||||
|
input_stream = enumerate(iter(input_sequence)) |
||||||
|
|
||||||
|
if reliable: |
||||||
|
generate_func = _generate_results |
||||||
|
else: |
||||||
|
generate_func = _generate_results_unreliable |
||||||
|
|
||||||
|
input_queue = PollableQueue(in_q_size) |
||||||
|
worker_output_queue = PollableQueue(8 * max_workers) |
||||||
|
output_queue = PollableQueue(out_q_size) |
||||||
|
|
||||||
|
# Start the worker threads. |
||||||
|
if async_inner: |
||||||
|
generator_func = inner_func |
||||||
|
else: |
||||||
|
args = (inner_func,) + args |
||||||
|
generator_func = _sync_inner_generator |
||||||
|
|
||||||
|
worker_threads = [] |
||||||
|
for _ in range(max_workers): |
||||||
|
t = threading.Thread( |
||||||
|
target=_async_streamer_async_inner, |
||||||
|
args=(input_queue, worker_output_queue, generator_func, args, kwargs)) |
||||||
|
t.daemon = True |
||||||
|
t.start() |
||||||
|
worker_threads.append(t) |
||||||
|
|
||||||
|
master_thread = threading.Thread( |
||||||
|
target=generate_func, |
||||||
|
args=(input_stream, input_queue, worker_output_queue, output_queue, max_workers, |
||||||
|
max_outstanding)) |
||||||
|
master_thread.daemon = True |
||||||
|
master_thread.start() |
||||||
|
|
||||||
|
try: |
||||||
|
while True: |
||||||
|
for value in output_queue.get_multiple(): |
||||||
|
if value is EndSentinel: |
||||||
|
return |
||||||
|
else: |
||||||
|
yield value |
||||||
|
finally: |
||||||
|
# Make sure work is done and the threads are stopped. |
||||||
|
for t in worker_threads: |
||||||
|
t.join(1) |
||||||
|
master_thread.join(1) |
||||||
|
|
||||||
|
input_queue.close() |
||||||
|
worker_output_queue.close() |
||||||
|
output_queue.close() |
||||||
|
|
||||||
|
return wrapper |
||||||
|
|
||||||
|
|
||||||
|
def async_generator(max_workers=1, |
||||||
|
in_q_size=10, |
||||||
|
out_q_size=12, |
||||||
|
max_outstanding=10000, |
||||||
|
async_inner=False, |
||||||
|
reliable=True): |
||||||
|
return ( |
||||||
|
lambda f: _async_generator(f, max_workers, in_q_size, out_q_size, max_outstanding, async_inner, reliable) |
||||||
|
) |
@ -0,0 +1,9 @@ |
|||||||
|
import os |
||||||
|
from tools.lib.file_helpers import mkdirs_exists_ok |
||||||
|
|
||||||
|
DEFAULT_CACHE_DIR = os.path.expanduser("~/.commacache") |
||||||
|
|
||||||
|
def cache_path_for_file_path(fn, cache_prefix=None): |
||||||
|
dir_ = os.path.join(DEFAULT_CACHE_DIR, "local") |
||||||
|
mkdirs_exists_ok(dir_) |
||||||
|
return os.path.join(dir_, os.path.abspath(fn).replace("/", "_")) |
@ -0,0 +1,2 @@ |
|||||||
|
class DataUnreadableError(Exception): |
||||||
|
pass |
@ -0,0 +1,23 @@ |
|||||||
|
import os |
||||||
|
from atomicwrites import AtomicWriter |
||||||
|
|
||||||
|
def atomic_write_in_dir(path, **kwargs): |
||||||
|
"""Creates an atomic writer using a temporary file in the same directory |
||||||
|
as the destination file. |
||||||
|
""" |
||||||
|
writer = AtomicWriter(path, **kwargs) |
||||||
|
return writer._open(_get_fileobject_func(writer, os.path.dirname(path))) |
||||||
|
|
||||||
|
def _get_fileobject_func(writer, temp_dir): |
||||||
|
def _get_fileobject(): |
||||||
|
file_obj = writer.get_fileobject(dir=temp_dir) |
||||||
|
os.chmod(file_obj.name, 0o644) |
||||||
|
return file_obj |
||||||
|
return _get_fileobject |
||||||
|
|
||||||
|
def mkdirs_exists_ok(path): |
||||||
|
try: |
||||||
|
os.makedirs(path) |
||||||
|
except OSError: |
||||||
|
if not os.path.isdir(path): |
||||||
|
raise |
@ -0,0 +1,3 @@ |
|||||||
|
def FileReader(fn): |
||||||
|
return open(fn, 'rb') |
||||||
|
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1 @@ |
|||||||
|
index_log |
@ -0,0 +1,19 @@ |
|||||||
|
CC := gcc
|
||||||
|
CXX := g++
|
||||||
|
PHONELIBS?=../../../../phonelibs
|
||||||
|
|
||||||
|
UNAME_S := $(shell uname -s)
|
||||||
|
ifeq ($(UNAME_S),Darwin) |
||||||
|
CAPNP_FLAGS := /usr/local/lib/libcapnp.a /usr/local/lib/libkj.a
|
||||||
|
else |
||||||
|
CAPNP_FLAGS := -I $(PHONELIBS)/capnp-cpp/include -I $(PHONELIBS)/capnp-cpp/include
|
||||||
|
CAPNP_LIBS := -L $(PHONELIBS)/capnp-cpp/x64/lib -L $(PHONELIBS)/capnp-cpp/x64/lib -l:libcapnp.a -l:libkj.a
|
||||||
|
endif |
||||||
|
|
||||||
|
index_log: index_log.cc |
||||||
|
$(eval $@_TMP := $(shell mktemp))
|
||||||
|
$(CXX) -std=gnu++11 -o $($@_TMP) \
|
||||||
|
index_log.cc \
|
||||||
|
$(CAPNP_FLAGS) \
|
||||||
|
$(CAPNP_LIBS)
|
||||||
|
mv $($@_TMP) $@
|
@ -0,0 +1,66 @@ |
|||||||
|
#include <cstdio> |
||||||
|
#include <cstdlib> |
||||||
|
#include <cassert> |
||||||
|
#include <string> |
||||||
|
|
||||||
|
#include <unistd.h> |
||||||
|
#include <fcntl.h> |
||||||
|
|
||||||
|
#include <sys/stat.h> |
||||||
|
#include <sys/mman.h> |
||||||
|
|
||||||
|
#include <kj/io.h> |
||||||
|
#include <capnp/serialize.h> |
||||||
|
|
||||||
|
int main(int argc, char** argv) { |
||||||
|
|
||||||
|
if (argc != 3) { |
||||||
|
printf("usage: %s <log_path> <index_output_path>\n", argv[0]); |
||||||
|
return 1; |
||||||
|
} |
||||||
|
|
||||||
|
const std::string log_fn = argv[1]; |
||||||
|
const std::string index_fn = argv[2]; |
||||||
|
|
||||||
|
int log_fd = open(log_fn.c_str(), O_RDONLY, 0); |
||||||
|
assert(log_fd >= 0); |
||||||
|
|
||||||
|
off_t log_size = lseek(log_fd, 0, SEEK_END); |
||||||
|
lseek(log_fd, 0, SEEK_SET); |
||||||
|
|
||||||
|
FILE* index_f = NULL; |
||||||
|
if (index_fn == "-") { |
||||||
|
index_f = stdout; |
||||||
|
} else { |
||||||
|
index_f = fopen(index_fn.c_str(), "wb"); |
||||||
|
} |
||||||
|
assert(index_f); |
||||||
|
|
||||||
|
void* log_data = mmap(NULL, log_size, PROT_READ, MAP_PRIVATE, log_fd, 0); |
||||||
|
assert(log_data); |
||||||
|
|
||||||
|
auto words = kj::arrayPtr((const capnp::word*)log_data, log_size/sizeof(capnp::word)); |
||||||
|
while (words.size() > 0) { |
||||||
|
|
||||||
|
uint64_t idx = ((uintptr_t)words.begin() - (uintptr_t)log_data); |
||||||
|
// printf("%llu - %ld\n", idx, words.size());
|
||||||
|
const char* idx_bytes = (const char*)&idx; |
||||||
|
fwrite(idx_bytes, 8, 1, index_f); |
||||||
|
try { |
||||||
|
capnp::FlatArrayMessageReader reader(words); |
||||||
|
words = kj::arrayPtr(reader.getEnd(), words.end()); |
||||||
|
} catch (kj::Exception exc) { |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
munmap(log_data, log_size); |
||||||
|
|
||||||
|
fclose(index_f); |
||||||
|
|
||||||
|
close(log_fd); |
||||||
|
|
||||||
|
return 0; |
||||||
|
} |
@ -0,0 +1,92 @@ |
|||||||
|
#!/usr/bin/env python |
||||||
|
import os |
||||||
|
import sys |
||||||
|
import termios |
||||||
|
import atexit |
||||||
|
from select import select |
||||||
|
|
||||||
|
class KBHit: |
||||||
|
|
||||||
|
def __init__(self): |
||||||
|
'''Creates a KBHit object that you can call to do various keyboard things. |
||||||
|
''' |
||||||
|
|
||||||
|
self.set_kbhit_terminal() |
||||||
|
|
||||||
|
def set_kbhit_terminal(self): |
||||||
|
# Save the terminal settings |
||||||
|
self.fd = sys.stdin.fileno() |
||||||
|
self.new_term = termios.tcgetattr(self.fd) |
||||||
|
self.old_term = termios.tcgetattr(self.fd) |
||||||
|
|
||||||
|
# New terminal setting unbuffered |
||||||
|
self.new_term[3] = (self.new_term[3] & ~termios.ICANON & ~termios.ECHO) |
||||||
|
termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.new_term) |
||||||
|
|
||||||
|
# Support normal-terminal reset at exit |
||||||
|
atexit.register(self.set_normal_term) |
||||||
|
|
||||||
|
def set_normal_term(self): |
||||||
|
''' Resets to normal terminal. On Windows this is a no-op. |
||||||
|
''' |
||||||
|
|
||||||
|
if os.name == 'nt': |
||||||
|
pass |
||||||
|
|
||||||
|
else: |
||||||
|
termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.old_term) |
||||||
|
|
||||||
|
|
||||||
|
def getch(self): |
||||||
|
''' Returns a keyboard character after kbhit() has been called. |
||||||
|
Should not be called in the same program as getarrow(). |
||||||
|
''' |
||||||
|
return sys.stdin.read(1) |
||||||
|
|
||||||
|
|
||||||
|
def getarrow(self): |
||||||
|
''' Returns an arrow-key code after kbhit() has been called. Codes are |
||||||
|
0 : up |
||||||
|
1 : right |
||||||
|
2 : down |
||||||
|
3 : left |
||||||
|
Should not be called in the same program as getch(). |
||||||
|
''' |
||||||
|
|
||||||
|
if os.name == 'nt': |
||||||
|
msvcrt.getch() # skip 0xE0 |
||||||
|
c = msvcrt.getch() |
||||||
|
vals = [72, 77, 80, 75] |
||||||
|
|
||||||
|
else: |
||||||
|
c = sys.stdin.read(3)[2] |
||||||
|
vals = [65, 67, 66, 68] |
||||||
|
|
||||||
|
return vals.index(ord(c.decode('utf-8'))) |
||||||
|
|
||||||
|
|
||||||
|
def kbhit(self): |
||||||
|
''' Returns True if keyboard character was hit, False otherwise. |
||||||
|
''' |
||||||
|
dr,dw,de = select([sys.stdin], [], [], 0) |
||||||
|
return dr != [] |
||||||
|
|
||||||
|
|
||||||
|
# Test |
||||||
|
if __name__ == "__main__": |
||||||
|
|
||||||
|
kb = KBHit() |
||||||
|
|
||||||
|
print('Hit any key, or ESC to exit') |
||||||
|
|
||||||
|
while True: |
||||||
|
|
||||||
|
if kb.kbhit(): |
||||||
|
c = kb.getch() |
||||||
|
if ord(c) == 27: # ESC |
||||||
|
break |
||||||
|
print(c) |
||||||
|
|
||||||
|
kb.set_normal_term() |
||||||
|
|
||||||
|
|
@ -0,0 +1,12 @@ |
|||||||
|
class lazy_property(object): |
||||||
|
"""Defines a property whose value will be computed only once and as needed. |
||||||
|
|
||||||
|
This can only be used on instance methods. |
||||||
|
""" |
||||||
|
def __init__(self, func): |
||||||
|
self._func = func |
||||||
|
|
||||||
|
def __get__(self, obj_self, cls): |
||||||
|
value = self._func(obj_self) |
||||||
|
setattr(obj_self, self._func.__name__, value) |
||||||
|
return value |
@ -0,0 +1,111 @@ |
|||||||
|
from cereal import log as capnp_log |
||||||
|
|
||||||
|
def write_can_to_msg(data, src, msg): |
||||||
|
if not isinstance(data[0], Sequence): |
||||||
|
data = [data] |
||||||
|
|
||||||
|
can_msgs = msg.init('can', len(data)) |
||||||
|
for i, d in enumerate(data): |
||||||
|
if d[0] < 0: continue # ios bug |
||||||
|
cc = can_msgs[i] |
||||||
|
cc.address = d[0] |
||||||
|
cc.busTime = 0 |
||||||
|
cc.dat = hex_to_str(d[2]) |
||||||
|
if len(d) == 4: |
||||||
|
cc.src = d[3] |
||||||
|
cc.busTime = d[1] |
||||||
|
else: |
||||||
|
cc.src = src |
||||||
|
|
||||||
|
def convert_old_pkt_to_new(old_pkt): |
||||||
|
m, d = old_pkt |
||||||
|
msg = capnp_log.Event.new_message() |
||||||
|
|
||||||
|
if len(m) == 3: |
||||||
|
_, pid, t = m |
||||||
|
msg.logMonoTime = t |
||||||
|
else: |
||||||
|
t, pid = m |
||||||
|
msg.logMonoTime = int(t * 1e9) |
||||||
|
|
||||||
|
last_velodyne_time = None |
||||||
|
|
||||||
|
if pid == PID_OBD: |
||||||
|
write_can_to_msg(d, 0, msg) |
||||||
|
elif pid == PID_CAM: |
||||||
|
frame = msg.init('frame') |
||||||
|
frame.frameId = d[0] |
||||||
|
frame.timestampEof = msg.logMonoTime |
||||||
|
# iOS |
||||||
|
elif pid == PID_IGPS: |
||||||
|
loc = msg.init('gpsLocation') |
||||||
|
loc.latitude = d[0] |
||||||
|
loc.longitude = d[1] |
||||||
|
loc.speed = d[2] |
||||||
|
loc.timestamp = int(m[0]*1000.0) # on iOS, first number is wall time in seconds |
||||||
|
loc.flags = 1 | 4 # has latitude, longitude, and speed. |
||||||
|
elif pid == PID_IMOTION: |
||||||
|
user_acceleration = d[:3] |
||||||
|
gravity = d[3:6] |
||||||
|
|
||||||
|
# iOS separates gravity from linear acceleration, so we recombine them. |
||||||
|
# Apple appears to use this constant for the conversion. |
||||||
|
g = -9.8 |
||||||
|
acceleration = [g*(a + b) for a, b in zip(user_acceleration, gravity)] |
||||||
|
|
||||||
|
accel_event = msg.init('sensorEvents', 1)[0] |
||||||
|
accel_event.acceleration.v = acceleration |
||||||
|
# android |
||||||
|
elif pid == PID_GPS: |
||||||
|
if len(d) <= 6 or d[-1] == "gps": |
||||||
|
loc = msg.init('gpsLocation') |
||||||
|
loc.latitude = d[0] |
||||||
|
loc.longitude = d[1] |
||||||
|
loc.speed = d[2] |
||||||
|
if len(d) > 6: |
||||||
|
loc.timestamp = d[6] |
||||||
|
loc.flags = 1 | 4 # has latitude, longitude, and speed. |
||||||
|
elif pid == PID_ACCEL: |
||||||
|
val = d[2] if type(d[2]) != type(0.0) else d |
||||||
|
accel_event = msg.init('sensorEvents', 1)[0] |
||||||
|
accel_event.acceleration.v = val |
||||||
|
elif pid == PID_GYRO: |
||||||
|
val = d[2] if type(d[2]) != type(0.0) else d |
||||||
|
gyro_event = msg.init('sensorEvents', 1)[0] |
||||||
|
gyro_event.init('gyro').v = val |
||||||
|
elif pid == PID_LIDAR: |
||||||
|
lid = msg.init('lidarPts') |
||||||
|
lid.idx = d[3] |
||||||
|
elif pid == PID_APPLANIX: |
||||||
|
loc = msg.init('liveLocation') |
||||||
|
loc.status = d[18] |
||||||
|
|
||||||
|
loc.lat, loc.lon, loc.alt = d[0:3] |
||||||
|
loc.vNED = d[3:6] |
||||||
|
|
||||||
|
loc.roll = d[6] |
||||||
|
loc.pitch = d[7] |
||||||
|
loc.heading = d[8] |
||||||
|
|
||||||
|
loc.wanderAngle = d[9] |
||||||
|
loc.trackAngle = d[10] |
||||||
|
|
||||||
|
loc.speed = d[11] |
||||||
|
|
||||||
|
loc.gyro = d[12:15] |
||||||
|
loc.accel = d[15:18] |
||||||
|
elif pid == PID_IBAROMETER: |
||||||
|
pressure_event = msg.init('sensorEvents', 1)[0] |
||||||
|
_, pressure = d[0:2] |
||||||
|
pressure_event.init('pressure').v = [pressure] # Kilopascals |
||||||
|
elif pid == PID_IINIT and len(d) == 4: |
||||||
|
init_event = msg.init('initData') |
||||||
|
init_event.deviceType = capnp_log.InitData.DeviceType.chffrIos |
||||||
|
|
||||||
|
build_info = init_event.init('iosBuildInfo') |
||||||
|
build_info.appVersion = d[0] |
||||||
|
build_info.appBuild = int(d[1]) |
||||||
|
build_info.osVersion = d[2] |
||||||
|
build_info.deviceModel = d[3] |
||||||
|
|
||||||
|
return msg.as_reader() |
@ -0,0 +1,205 @@ |
|||||||
|
import os |
||||||
|
import sys |
||||||
|
import gzip |
||||||
|
import zlib |
||||||
|
import json |
||||||
|
import bz2 |
||||||
|
import tempfile |
||||||
|
import requests |
||||||
|
import subprocess |
||||||
|
from aenum import Enum |
||||||
|
import capnp |
||||||
|
import numpy as np |
||||||
|
|
||||||
|
import platform |
||||||
|
|
||||||
|
from tools.lib.exceptions import DataUnreadableError |
||||||
|
try: |
||||||
|
from xx.chffr.lib.filereader import FileReader |
||||||
|
except ImportError: |
||||||
|
from tools.lib.filereader import FileReader |
||||||
|
from tools.lib.log_util import convert_old_pkt_to_new |
||||||
|
from cereal import log as capnp_log |
||||||
|
|
||||||
|
OP_PATH = os.path.dirname(os.path.dirname(capnp_log.__file__)) |
||||||
|
|
||||||
|
def index_log(fn): |
||||||
|
index_log_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "index_log") |
||||||
|
index_log = os.path.join(index_log_dir, "index_log") |
||||||
|
phonelibs_dir = os.path.join(OP_PATH, 'phonelibs') |
||||||
|
|
||||||
|
subprocess.check_call(["make", "PHONELIBS=" + phonelibs_dir], cwd=index_log_dir, stdout=subprocess.DEVNULL) |
||||||
|
|
||||||
|
try: |
||||||
|
dat = subprocess.check_output([index_log, fn, "-"]) |
||||||
|
except subprocess.CalledProcessError: |
||||||
|
raise DataUnreadableError("%s capnp is corrupted/truncated" % fn) |
||||||
|
return np.frombuffer(dat, dtype=np.uint64) |
||||||
|
|
||||||
|
def event_read_multiple_bytes(dat): |
||||||
|
with tempfile.NamedTemporaryFile() as dat_f: |
||||||
|
dat_f.write(dat) |
||||||
|
dat_f.flush() |
||||||
|
idx = index_log(dat_f.name) |
||||||
|
|
||||||
|
end_idx = np.uint64(len(dat)) |
||||||
|
idx = np.append(idx, end_idx) |
||||||
|
|
||||||
|
return [capnp_log.Event.from_bytes(dat[idx[i]:idx[i+1]]) |
||||||
|
for i in range(len(idx)-1)] |
||||||
|
|
||||||
|
|
||||||
|
# this is an iterator itself, and uses private variables from LogReader |
||||||
|
class MultiLogIterator(object): |
||||||
|
def __init__(self, log_paths, wraparound=True): |
||||||
|
self._log_paths = log_paths |
||||||
|
self._wraparound = wraparound |
||||||
|
|
||||||
|
self._first_log_idx = next(i for i in range(len(log_paths)) if log_paths[i] is not None) |
||||||
|
self._current_log = self._first_log_idx |
||||||
|
self._idx = 0 |
||||||
|
self._log_readers = [None]*len(log_paths) |
||||||
|
self.start_time = self._log_reader(self._first_log_idx)._ts[0] |
||||||
|
|
||||||
|
def _log_reader(self, i): |
||||||
|
if self._log_readers[i] is None and self._log_paths[i] is not None: |
||||||
|
log_path = self._log_paths[i] |
||||||
|
print("LogReader:%s" % log_path) |
||||||
|
self._log_readers[i] = LogReader(log_path) |
||||||
|
|
||||||
|
return self._log_readers[i] |
||||||
|
|
||||||
|
def __iter__(self): |
||||||
|
return self |
||||||
|
|
||||||
|
def _inc(self): |
||||||
|
lr = self._log_reader(self._current_log) |
||||||
|
if self._idx < len(lr._ents)-1: |
||||||
|
self._idx += 1 |
||||||
|
else: |
||||||
|
self._idx = 0 |
||||||
|
self._current_log = next(i for i in range(self._current_log + 1, len(self._log_readers) + 1) if i == len(self._log_readers) or self._log_paths[i] is not None) |
||||||
|
# wraparound |
||||||
|
if self._current_log == len(self._log_readers): |
||||||
|
if self._wraparound: |
||||||
|
self._current_log = self._first_log_idx |
||||||
|
else: |
||||||
|
raise StopIteration |
||||||
|
|
||||||
|
def __next__(self): |
||||||
|
while 1: |
||||||
|
lr = self._log_reader(self._current_log) |
||||||
|
ret = lr._ents[self._idx] |
||||||
|
if lr._do_conversion: |
||||||
|
ret = convert_old_pkt_to_new(ret, lr.data_version) |
||||||
|
self._inc() |
||||||
|
return ret |
||||||
|
|
||||||
|
def tell(self): |
||||||
|
# returns seconds from start of log |
||||||
|
return (self._log_reader(self._current_log)._ts[self._idx] - self.start_time) * 1e-9 |
||||||
|
|
||||||
|
def seek(self, ts): |
||||||
|
# seek to nearest minute |
||||||
|
minute = int(ts/60) |
||||||
|
if minute >= len(self._log_paths) or self._log_paths[minute] is None: |
||||||
|
return False |
||||||
|
|
||||||
|
self._current_log = minute |
||||||
|
|
||||||
|
# HACK: O(n) seek afterward |
||||||
|
self._idx = 0 |
||||||
|
while self.tell() < ts: |
||||||
|
self._inc() |
||||||
|
return True |
||||||
|
|
||||||
|
|
||||||
|
class LogReader(object): |
||||||
|
def __init__(self, fn, canonicalize=True, only_union_types=False): |
||||||
|
_, ext = os.path.splitext(fn) |
||||||
|
data_version = None |
||||||
|
|
||||||
|
with FileReader(fn) as f: |
||||||
|
dat = f.read() |
||||||
|
|
||||||
|
# decompress file |
||||||
|
if ext == ".gz" and ("log_" in fn or "log2" in fn): |
||||||
|
dat = zlib.decompress(dat, zlib.MAX_WBITS|32) |
||||||
|
elif ext == ".bz2": |
||||||
|
dat = bz2.decompress(dat) |
||||||
|
elif ext == ".7z": |
||||||
|
if platform.system() == "Darwin": |
||||||
|
os.environ["LA_LIBRARY_FILEPATH"] = "/usr/local/opt/libarchive/lib/libarchive.dylib" |
||||||
|
import libarchive.public |
||||||
|
with libarchive.public.memory_reader(dat) as aa: |
||||||
|
mdat = [] |
||||||
|
for it in aa: |
||||||
|
for bb in it.get_blocks(): |
||||||
|
mdat.append(bb) |
||||||
|
dat = ''.join(mdat) |
||||||
|
|
||||||
|
# TODO: extension shouln't be a proxy for DeviceType |
||||||
|
if ext == "": |
||||||
|
if dat[0] == "[": |
||||||
|
needs_conversion = True |
||||||
|
ents = [json.loads(x) for x in dat.strip().split("\n")[:-1]] |
||||||
|
if "_" in fn: |
||||||
|
data_version = fn.split("_")[1] |
||||||
|
else: |
||||||
|
# old rlogs weren't bz2 compressed |
||||||
|
needs_conversion = False |
||||||
|
ents = event_read_multiple_bytes(dat) |
||||||
|
elif ext == ".gz": |
||||||
|
if "log_" in fn: |
||||||
|
# Zero data file. |
||||||
|
ents = [json.loads(x) for x in dat.strip().split("\n")[:-1]] |
||||||
|
needs_conversion = True |
||||||
|
elif "log2" in fn: |
||||||
|
needs_conversion = False |
||||||
|
ents = event_read_multiple_bytes(dat) |
||||||
|
else: |
||||||
|
raise Exception("unknown extension") |
||||||
|
elif ext == ".bz2": |
||||||
|
needs_conversion = False |
||||||
|
ents = event_read_multiple_bytes(dat) |
||||||
|
elif ext == ".7z": |
||||||
|
needs_conversion = True |
||||||
|
ents = [json.loads(x) for x in dat.strip().split("\n")] |
||||||
|
else: |
||||||
|
raise Exception("unknown extension") |
||||||
|
|
||||||
|
if needs_conversion: |
||||||
|
# TODO: should we call convert_old_pkt_to_new to generate this? |
||||||
|
self._ts = [x[0][0]*1e9 for x in ents] |
||||||
|
else: |
||||||
|
self._ts = [x.logMonoTime for x in ents] |
||||||
|
|
||||||
|
self.data_version = data_version |
||||||
|
self._do_conversion = needs_conversion and canonicalize |
||||||
|
self._only_union_types = only_union_types |
||||||
|
self._ents = ents |
||||||
|
|
||||||
|
def __iter__(self): |
||||||
|
for ent in self._ents: |
||||||
|
if self._do_conversion: |
||||||
|
yield convert_old_pkt_to_new(ent, self.data_version) |
||||||
|
elif self._only_union_types: |
||||||
|
try: |
||||||
|
ent.which() |
||||||
|
yield ent |
||||||
|
except capnp.lib.capnp.KjException: |
||||||
|
pass |
||||||
|
else: |
||||||
|
yield ent |
||||||
|
|
||||||
|
def load_many_logs_canonical(log_paths): |
||||||
|
"""Load all logs for a sequence of log paths.""" |
||||||
|
for log_path in log_paths: |
||||||
|
for msg in LogReader(log_path): |
||||||
|
yield msg |
||||||
|
|
||||||
|
if __name__ == "__main__": |
||||||
|
log_path = sys.argv[1] |
||||||
|
lr = LogReader(log_path) |
||||||
|
for msg in lr: |
||||||
|
print(msg) |
@ -0,0 +1,24 @@ |
|||||||
|
Simple easy-to-use hacky matroska parser |
||||||
|
|
||||||
|
Define your handler class: |
||||||
|
|
||||||
|
class MyMatroskaHandler(mkvparse.MatroskaHandler): |
||||||
|
def tracks_available(self): |
||||||
|
... |
||||||
|
|
||||||
|
def segment_info_available(self): |
||||||
|
... |
||||||
|
|
||||||
|
def frame(self, track_id, timestamp, data, more_laced_blocks, duration, keyframe_flag, invisible_flag, discardable_flag): |
||||||
|
... |
||||||
|
|
||||||
|
and `mkvparse.mkvparse(file, MyMatroskaHandler())` |
||||||
|
|
||||||
|
|
||||||
|
Supports lacing and setting global timecode scale, subtitles (BlockGroup). Does not support cues, tags, chapters, seeking and so on. Supports resyncing when something bad is encountered in matroska stream. |
||||||
|
|
||||||
|
Also contains example of generation of Matroska files from python |
||||||
|
|
||||||
|
Subtitles should remain as text, binary data gets encoded to hex. |
||||||
|
|
||||||
|
Licence=MIT |
@ -0,0 +1,188 @@ |
|||||||
|
#!/usr/bin/env python |
||||||
|
import sys |
||||||
|
import random |
||||||
|
import math |
||||||
|
|
||||||
|
# Simple hacky Matroska generator |
||||||
|
# Reads mp3 file "q.mp3" and jpeg images from img/0.jpg, img/1.jpg and so on and |
||||||
|
# writes Matroska file with mjpeg and mp3 to stdout |
||||||
|
|
||||||
|
# License=MIT |
||||||
|
|
||||||
|
# unsigned |
||||||
|
def big_endian_number(number): |
||||||
|
if(number<0x100): |
||||||
|
return chr(number) |
||||||
|
return big_endian_number(number>>8) + chr(number&0xFF) |
||||||
|
|
||||||
|
ben=big_endian_number |
||||||
|
|
||||||
|
def ebml_encode_number(number): |
||||||
|
def trailing_bits(rest_of_number, number_of_bits): |
||||||
|
# like big_endian_number, but can do padding zeroes |
||||||
|
if number_of_bits==8: |
||||||
|
return chr(rest_of_number&0xFF); |
||||||
|
else: |
||||||
|
return trailing_bits(rest_of_number>>8, number_of_bits-8) + chr(rest_of_number&0xFF) |
||||||
|
|
||||||
|
if number == -1: |
||||||
|
return chr(0xFF) |
||||||
|
if number < 2**7 - 1: |
||||||
|
return chr(number|0x80) |
||||||
|
if number < 2**14 - 1: |
||||||
|
return chr(0x40 | (number>>8)) + trailing_bits(number, 8) |
||||||
|
if number < 2**21 - 1: |
||||||
|
return chr(0x20 | (number>>16)) + trailing_bits(number, 16) |
||||||
|
if number < 2**28 - 1: |
||||||
|
return chr(0x10 | (number>>24)) + trailing_bits(number, 24) |
||||||
|
if number < 2**35 - 1: |
||||||
|
return chr(0x08 | (number>>32)) + trailing_bits(number, 32) |
||||||
|
if number < 2**42 - 1: |
||||||
|
return chr(0x04 | (number>>40)) + trailing_bits(number, 40) |
||||||
|
if number < 2**49 - 1: |
||||||
|
return chr(0x02 | (number>>48)) + trailing_bits(number, 48) |
||||||
|
if number < 2**56 - 1: |
||||||
|
return chr(0x01) + trailing_bits(number, 56) |
||||||
|
raise Exception("NUMBER TOO BIG") |
||||||
|
|
||||||
|
def ebml_element(element_id, data, length=None): |
||||||
|
if length==None: |
||||||
|
length = len(data) |
||||||
|
return big_endian_number(element_id) + ebml_encode_number(length) + data |
||||||
|
|
||||||
|
|
||||||
|
def write_ebml_header(f, content_type, version, read_version): |
||||||
|
f.write( |
||||||
|
ebml_element(0x1A45DFA3, "" # EBML |
||||||
|
+ ebml_element(0x4286, ben(1)) # EBMLVersion |
||||||
|
+ ebml_element(0x42F7, ben(1)) # EBMLReadVersion |
||||||
|
+ ebml_element(0x42F2, ben(4)) # EBMLMaxIDLength |
||||||
|
+ ebml_element(0x42F3, ben(8)) # EBMLMaxSizeLength |
||||||
|
+ ebml_element(0x4282, content_type) # DocType |
||||||
|
+ ebml_element(0x4287, ben(version)) # DocTypeVersion |
||||||
|
+ ebml_element(0x4285, ben(read_version)) # DocTypeReadVersion |
||||||
|
)) |
||||||
|
|
||||||
|
def write_infinite_segment_header(f): |
||||||
|
# write segment element header |
||||||
|
f.write(ebml_element(0x18538067,"",-1)) # Segment (unknown length) |
||||||
|
|
||||||
|
def random_uid(): |
||||||
|
def rint(): |
||||||
|
return int(random.random()*(0x100**4)) |
||||||
|
return ben(rint()) + ben(rint()) + ben(rint()) + ben(rint()) |
||||||
|
|
||||||
|
|
||||||
|
def example(): |
||||||
|
write_ebml_header(sys.stdout, "matroska", 2, 2) |
||||||
|
write_infinite_segment_header(sys.stdout) |
||||||
|
|
||||||
|
|
||||||
|
# write segment info (optional) |
||||||
|
sys.stdout.write(ebml_element(0x1549A966, "" # SegmentInfo |
||||||
|
+ ebml_element(0x73A4, random_uid()) # SegmentUID |
||||||
|
+ ebml_element(0x7BA9, "mkvgen.py test") # Title |
||||||
|
+ ebml_element(0x4D80, "mkvgen.py") # MuxingApp |
||||||
|
+ ebml_element(0x5741, "mkvgen.py") # WritingApp |
||||||
|
)) |
||||||
|
|
||||||
|
# write trans data (codecs etc.) |
||||||
|
sys.stdout.write(ebml_element(0x1654AE6B, "" # Tracks |
||||||
|
+ ebml_element(0xAE, "" # TrackEntry |
||||||
|
+ ebml_element(0xD7, ben(1)) # TrackNumber |
||||||
|
+ ebml_element(0x73C5, ben(0x77)) # TrackUID |
||||||
|
+ ebml_element(0x83, ben(0x01)) # TrackType |
||||||
|
#0x01 track is a video track |
||||||
|
#0x02 track is an audio track |
||||||
|
#0x03 track is a complex track, i.e. a combined video and audio track |
||||||
|
#0x10 track is a logo track |
||||||
|
#0x11 track is a subtitle track |
||||||
|
#0x12 track is a button track |
||||||
|
#0x20 track is a control track |
||||||
|
+ ebml_element(0x536E, "mjpeg data") # Name |
||||||
|
+ ebml_element(0x86, "V_MJPEG") # CodecID |
||||||
|
#+ ebml_element(0x23E383, ben(100000000)) # DefaultDuration (opt.), nanoseconds |
||||||
|
#+ ebml_element(0x6DE7, ben(100)) # MinCache |
||||||
|
+ ebml_element(0xE0, "" # Video |
||||||
|
+ ebml_element(0xB0, ben(640)) # PixelWidth |
||||||
|
+ ebml_element(0xBA, ben(480)) # PixelHeight |
||||||
|
) |
||||||
|
) |
||||||
|
+ ebml_element(0xAE, "" # TrackEntry |
||||||
|
+ ebml_element(0xD7, ben(2)) # TrackNumber |
||||||
|
+ ebml_element(0x73C5, ben(0x78)) # TrackUID |
||||||
|
+ ebml_element(0x83, ben(0x02)) # TrackType |
||||||
|
#0x01 track is a video track |
||||||
|
#0x02 track is an audio track |
||||||
|
#0x03 track is a complex track, i.e. a combined video and audio track |
||||||
|
#0x10 track is a logo track |
||||||
|
#0x11 track is a subtitle track |
||||||
|
#0x12 track is a button track |
||||||
|
#0x20 track is a control track |
||||||
|
+ ebml_element(0x536E, "content of mp3 file") # Name |
||||||
|
#+ ebml_element(0x6DE7, ben(100)) # MinCache |
||||||
|
+ ebml_element(0x86, "A_MPEG/L3") # CodecID |
||||||
|
#+ ebml_element(0xE1, "") # Audio |
||||||
|
) |
||||||
|
)) |
||||||
|
|
||||||
|
|
||||||
|
mp3file = open("q.mp3", "rb") |
||||||
|
mp3file.read(500000); |
||||||
|
|
||||||
|
def mp3framesgenerator(f): |
||||||
|
debt="" |
||||||
|
while True: |
||||||
|
for i in xrange(0,len(debt)+1): |
||||||
|
if i >= len(debt)-1: |
||||||
|
debt = debt + f.read(8192) |
||||||
|
break |
||||||
|
#sys.stderr.write("i="+str(i)+" len="+str(len(debt))+"\n") |
||||||
|
if ord(debt[i])==0xFF and (ord(debt[i+1]) & 0xF0)==0XF0 and i>700: |
||||||
|
if i>0: |
||||||
|
yield debt[0:i] |
||||||
|
# sys.stderr.write("len="+str(i)+"\n") |
||||||
|
debt = debt[i:] |
||||||
|
break |
||||||
|
|
||||||
|
|
||||||
|
mp3 = mp3framesgenerator(mp3file) |
||||||
|
mp3.next() |
||||||
|
|
||||||
|
|
||||||
|
for i in xrange(0,530): |
||||||
|
framefile = open("img/"+str(i)+".jpg", "rb") |
||||||
|
framedata = framefile.read() |
||||||
|
framefile.close() |
||||||
|
|
||||||
|
# write cluster (actual video data) |
||||||
|
|
||||||
|
if random.random()<1: |
||||||
|
sys.stdout.write(ebml_element(0x1F43B675, "" # Cluster |
||||||
|
+ ebml_element(0xE7, ben(int(i*26*4))) # TimeCode, uint, milliseconds |
||||||
|
# + ebml_element(0xA7, ben(0)) # Position, uint |
||||||
|
+ ebml_element(0xA3, "" # SimpleBlock |
||||||
|
+ ebml_encode_number(1) # track number |
||||||
|
+ chr(0x00) + chr(0x00) # timecode, relative to Cluster timecode, sint16, in milliseconds |
||||||
|
+ chr(0x00) # flags |
||||||
|
+ framedata |
||||||
|
))) |
||||||
|
|
||||||
|
for u in xrange(0,4): |
||||||
|
mp3f=mp3.next() |
||||||
|
if random.random()<1: |
||||||
|
sys.stdout.write(ebml_element(0x1F43B675, "" # Cluster |
||||||
|
+ ebml_element(0xE7, ben(i*26*4+u*26)) # TimeCode, uint, milliseconds |
||||||
|
+ ebml_element(0xA3, "" # SimpleBlock |
||||||
|
+ ebml_encode_number(2) # track number |
||||||
|
+ chr(0x00) + chr(0x00) # timecode, relative to Cluster timecode, sint16, in milliseconds |
||||||
|
+ chr(0x00) # flags |
||||||
|
+ mp3f |
||||||
|
))) |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__': |
||||||
|
example() |
@ -0,0 +1,87 @@ |
|||||||
|
#!/usr/bin/env python |
||||||
|
# Copyright (c) 2016, Comma.ai, Inc. |
||||||
|
|
||||||
|
import sys |
||||||
|
import re |
||||||
|
import binascii |
||||||
|
|
||||||
|
from tools.lib.mkvparse import mkvparse |
||||||
|
from tools.lib.mkvparse import mkvgen |
||||||
|
from tools.lib.mkvparse.mkvgen import ben, ebml_element, ebml_encode_number |
||||||
|
|
||||||
|
class MatroskaIndex(mkvparse.MatroskaHandler): |
||||||
|
# def __init__(self, banlist, nocluster_mode): |
||||||
|
# pass |
||||||
|
def __init__(self): |
||||||
|
self.frameindex = [] |
||||||
|
|
||||||
|
def tracks_available(self): |
||||||
|
_, self.config_record = self.tracks[1]['CodecPrivate'] |
||||||
|
|
||||||
|
def frame(self, track_id, timestamp, pos, length, more_laced_frames, duration, |
||||||
|
keyframe, invisible, discardable): |
||||||
|
self.frameindex.append((pos, length, keyframe)) |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def mkvindex(f): |
||||||
|
handler = MatroskaIndex() |
||||||
|
mkvparse.mkvparse(f, handler) |
||||||
|
return handler.config_record, handler.frameindex |
||||||
|
|
||||||
|
|
||||||
|
def simple_gen(of, config_record, w, h, framedata): |
||||||
|
mkvgen.write_ebml_header(of, "matroska", 2, 2) |
||||||
|
mkvgen.write_infinite_segment_header(of) |
||||||
|
|
||||||
|
of.write(ebml_element(0x1654AE6B, "" # Tracks |
||||||
|
+ ebml_element(0xAE, "" # TrackEntry |
||||||
|
+ ebml_element(0xD7, ben(1)) # TrackNumber |
||||||
|
+ ebml_element(0x73C5, ben(1)) # TrackUID |
||||||
|
+ ebml_element(0x83, ben(1)) # TrackType = video track |
||||||
|
+ ebml_element(0x86, "V_MS/VFW/FOURCC") # CodecID |
||||||
|
+ ebml_element(0xE0, "" # Video |
||||||
|
+ ebml_element(0xB0, ben(w)) # PixelWidth |
||||||
|
+ ebml_element(0xBA, ben(h)) # PixelHeight |
||||||
|
) |
||||||
|
+ ebml_element(0x63A2, config_record) # CodecPrivate (ffv1 configuration record) |
||||||
|
) |
||||||
|
)) |
||||||
|
|
||||||
|
blocks = [] |
||||||
|
for fd in framedata: |
||||||
|
blocks.append( |
||||||
|
ebml_element(0xA3, "" # SimpleBlock |
||||||
|
+ ebml_encode_number(1) # track number |
||||||
|
+ chr(0x00) + chr(0x00) # timecode, relative to Cluster timecode, sint16, in milliseconds |
||||||
|
+ chr(0x80) # flags (keyframe) |
||||||
|
+ fd |
||||||
|
) |
||||||
|
) |
||||||
|
|
||||||
|
of.write(ebml_element(0x1F43B675, "" # Cluster |
||||||
|
+ ebml_element(0xE7, ben(0)) # TimeCode, uint, milliseconds |
||||||
|
# + ebml_element(0xA7, ben(0)) # Position, uint |
||||||
|
+ ''.join(blocks))) |
||||||
|
|
||||||
|
if __name__ == "__main__": |
||||||
|
import random |
||||||
|
|
||||||
|
if len(sys.argv) != 2: |
||||||
|
print("usage: %s mkvpath" % sys.argv[0]) |
||||||
|
with open(sys.argv[1], "rb") as f: |
||||||
|
cr, index = mkvindex(f) |
||||||
|
|
||||||
|
# cr = "280000003002000030010000010018004646563100cb070000000000000000000000000000000000".decode("hex") |
||||||
|
|
||||||
|
def geti(i): |
||||||
|
pos, length = index[i] |
||||||
|
with open(sys.argv[1], "rb") as f: |
||||||
|
f.seek(pos) |
||||||
|
return f.read(length) |
||||||
|
|
||||||
|
dats = [geti(random.randrange(200)) for _ in xrange(30)] |
||||||
|
|
||||||
|
with open("tmpout.mkv", "wb") as of: |
||||||
|
simple_gen(of, cr, dats) |
||||||
|
|
@ -0,0 +1,761 @@ |
|||||||
|
# Licence==MIT; Vitaly "_Vi" Shukela 2012 |
||||||
|
|
||||||
|
# Simple easy-to-use hacky matroska parser |
||||||
|
|
||||||
|
# Supports SimpleBlock and BlockGroup, lacing, TimecodeScale. |
||||||
|
# Does not support seeking, cues, chapters and other features. |
||||||
|
# No proper EOF handling unfortunately |
||||||
|
|
||||||
|
# See "mkvuser.py" for the example |
||||||
|
|
||||||
|
import traceback |
||||||
|
from struct import unpack |
||||||
|
|
||||||
|
import sys |
||||||
|
import datetime |
||||||
|
|
||||||
|
if sys.version < '3': |
||||||
|
range=xrange |
||||||
|
else: |
||||||
|
#identity=lambda x:x |
||||||
|
def ord(something): |
||||||
|
if type(something)==bytes: |
||||||
|
if something == b"": |
||||||
|
raise StopIteration |
||||||
|
return something[0] |
||||||
|
else: |
||||||
|
return something |
||||||
|
|
||||||
|
def get_major_bit_number(n): |
||||||
|
''' |
||||||
|
Takes uint8, returns number of the most significant bit plus the number with that bit cleared. |
||||||
|
Examples: |
||||||
|
0b10010101 -> (0, 0b00010101) |
||||||
|
0b00010101 -> (3, 0b00000101) |
||||||
|
0b01111111 -> (1, 0b00111111) |
||||||
|
''' |
||||||
|
if not n: |
||||||
|
raise Exception("Bad number") |
||||||
|
i=0x80; |
||||||
|
r=0 |
||||||
|
while not n&i: |
||||||
|
r+=1 |
||||||
|
i>>=1 |
||||||
|
return (r,n&~i); |
||||||
|
|
||||||
|
def read_matroska_number(f, unmodified=False, signed=False): |
||||||
|
''' |
||||||
|
Read ebml number. Unmodified means don't clear the length bit (as in Element IDs) |
||||||
|
Returns the number and it's length as a tuple |
||||||
|
|
||||||
|
See examples in "parse_matroska_number" function |
||||||
|
''' |
||||||
|
if unmodified and signed: |
||||||
|
raise Exception("Contradictary arguments") |
||||||
|
first_byte=f.read(1) |
||||||
|
if(first_byte==""): |
||||||
|
raise StopIteration |
||||||
|
r = ord(first_byte) |
||||||
|
(n,r2) = get_major_bit_number(r) |
||||||
|
if not unmodified: |
||||||
|
r=r2 |
||||||
|
# from now "signed" means "negative" |
||||||
|
i=n |
||||||
|
while i: |
||||||
|
r = r * 0x100 + ord(f.read(1)) |
||||||
|
i-=1 |
||||||
|
if signed: |
||||||
|
r-=(2**(7*n+7)-1) |
||||||
|
else: |
||||||
|
if r==2**(7*n+7)-1: |
||||||
|
return (-1, n+1) |
||||||
|
return (r,n+1) |
||||||
|
|
||||||
|
def parse_matroska_number(data, pos, unmodified=False, signed=False): |
||||||
|
''' |
||||||
|
Parse ebml number from buffer[pos:]. Just like read_matroska_number. |
||||||
|
Unmodified means don't clear the length bit (as in Element IDs) |
||||||
|
Returns the number plus the new position in input buffer |
||||||
|
|
||||||
|
Examples: |
||||||
|
"\x81" -> (1, pos+1) |
||||||
|
"\x40\x01" -> (1, pos+2) |
||||||
|
"\x20\x00\x01" -> (1, pos+3) |
||||||
|
"\x3F\xFF\xFF" -> (0x1FFFFF, pos+3) |
||||||
|
"\x20\x00\x01" unmodified -> (0x200001, pos+3) |
||||||
|
"\xBF" signed -> (0, pos+1) |
||||||
|
"\xBE" signed -> (-1, pos+1) |
||||||
|
"\xC0" signed -> (1, pos+1) |
||||||
|
"\x5F\xEF" signed -> (-16, pos+2) |
||||||
|
''' |
||||||
|
if unmodified and signed: |
||||||
|
raise Exception("Contradictary arguments") |
||||||
|
r = ord(data[pos]) |
||||||
|
pos+=1 |
||||||
|
(n,r2) = get_major_bit_number(r) |
||||||
|
if not unmodified: |
||||||
|
r=r2 |
||||||
|
# from now "signed" means "negative" |
||||||
|
i=n |
||||||
|
while i: |
||||||
|
r = r * 0x100 + ord(data[pos]) |
||||||
|
pos+=1 |
||||||
|
i-=1 |
||||||
|
if signed: |
||||||
|
r-=(2**(7*n+6)-1) |
||||||
|
else: |
||||||
|
if r==2**(7*n+7)-1: |
||||||
|
return (-1, pos) |
||||||
|
return (r,pos) |
||||||
|
|
||||||
|
def parse_xiph_number(data, pos): |
||||||
|
''' |
||||||
|
Parse the Xiph lacing number from data[pos:] |
||||||
|
Returns the number plus the new position |
||||||
|
|
||||||
|
Examples: |
||||||
|
"\x01" -> (1, pos+1) |
||||||
|
"\x55" -> (0x55, pos+1) |
||||||
|
"\xFF\x04" -> (0x103, pos+2) |
||||||
|
"\xFF\xFF\x04" -> (0x202, pos+3) |
||||||
|
"\xFF\xFF\x00" -> (0x1FE, pos+3) |
||||||
|
''' |
||||||
|
v = ord(data[pos]) |
||||||
|
pos+=1 |
||||||
|
|
||||||
|
r=0 |
||||||
|
while v==255: |
||||||
|
r+=v |
||||||
|
v = ord(data[pos]) |
||||||
|
pos+=1 |
||||||
|
|
||||||
|
r+=v |
||||||
|
return (r, pos) |
||||||
|
|
||||||
|
|
||||||
|
def parse_fixedlength_number(data, pos, length, signed=False): |
||||||
|
''' |
||||||
|
Read the big-endian number from data[pos:pos+length] |
||||||
|
Returns the number plus the new position |
||||||
|
|
||||||
|
Examples: |
||||||
|
"\x01" -> (0x1, pos+1) |
||||||
|
"\x55" -> (0x55, pos+1) |
||||||
|
"\x55" signed -> (0x55, pos+1) |
||||||
|
"\xFF\x04" -> (0xFF04, pos+2) |
||||||
|
"\xFF\x04" signed -> (-0x00FC, pos+2) |
||||||
|
''' |
||||||
|
r=0 |
||||||
|
for i in range(length): |
||||||
|
r=r*0x100+ord(data[pos+i]) |
||||||
|
if signed: |
||||||
|
if ord(data[pos]) & 0x80: |
||||||
|
r-=2**(8*length) |
||||||
|
return (r, pos+length) |
||||||
|
|
||||||
|
def read_fixedlength_number(f, length, signed=False): |
||||||
|
""" Read length bytes and parse (parse_fixedlength_number) it. |
||||||
|
Returns only the number""" |
||||||
|
buf = f.read(length) |
||||||
|
(r, pos) = parse_fixedlength_number(buf, 0, length, signed) |
||||||
|
return r |
||||||
|
|
||||||
|
def read_ebml_element_header(f): |
||||||
|
''' |
||||||
|
Read Element ID and size |
||||||
|
Returns id, element size and this header size |
||||||
|
''' |
||||||
|
(id_, n) = read_matroska_number(f, unmodified=True) |
||||||
|
(size, n2) = read_matroska_number(f) |
||||||
|
return (id_, size, n+n2) |
||||||
|
|
||||||
|
class EbmlElementType: |
||||||
|
VOID=0 |
||||||
|
MASTER=1 # read all subelements and return tree. Don't use this too large things like Segment |
||||||
|
UNSIGNED=2 |
||||||
|
SIGNED=3 |
||||||
|
TEXTA=4 |
||||||
|
TEXTU=5 |
||||||
|
BINARY=6 |
||||||
|
FLOAT=7 |
||||||
|
DATE=8 |
||||||
|
|
||||||
|
JUST_GO_ON=10 # For "Segment". |
||||||
|
# Actually MASTER, but don't build the tree for all subelements, |
||||||
|
# interpreting all child elements as if they were top-level elements |
||||||
|
|
||||||
|
|
||||||
|
EET=EbmlElementType |
||||||
|
|
||||||
|
# lynx -width=10000 -dump http://matroska.org/technical/specs/index.html |
||||||
|
# | sed 's/not 0/not0/g; s/> 0/>0/g; s/Sampling Frequency/SamplingFrequency/g' |
||||||
|
# | awk '{print $1 " " $3 " " $8}' |
||||||
|
# | grep '\[..\]' |
||||||
|
# | perl -ne '/(\S+) (\S+) (.)/; |
||||||
|
# $name=$1; $id=$2; $type=$3; |
||||||
|
# $id=~s/\[|\]//g; |
||||||
|
# %types = (m=>"EET.MASTER", |
||||||
|
# u=>"EET.UNSIGNED", |
||||||
|
# i=>"EET.SIGNED", |
||||||
|
# 8=>"EET.TEXTU", |
||||||
|
# s=>"EET.TEXTA", |
||||||
|
# b=>"EET.BINARY", |
||||||
|
# f=>"EET.FLOAT", |
||||||
|
# d=>"EET.DATE"); |
||||||
|
# $t=$types{$type}; |
||||||
|
# next unless $t; |
||||||
|
# $t="EET.JUST_GO_ON" if $name eq "Segment" or $name eq "Cluster"; |
||||||
|
# print "\t0x$id: ($t, \"$name\"),\n";' |
||||||
|
|
||||||
|
element_types_names = { |
||||||
|
0x1A45DFA3: (EET.MASTER, "EBML"), |
||||||
|
0x4286: (EET.UNSIGNED, "EBMLVersion"), |
||||||
|
0x42F7: (EET.UNSIGNED, "EBMLReadVersion"), |
||||||
|
0x42F2: (EET.UNSIGNED, "EBMLMaxIDLength"), |
||||||
|
0x42F3: (EET.UNSIGNED, "EBMLMaxSizeLength"), |
||||||
|
0x4282: (EET.TEXTA, "DocType"), |
||||||
|
0x4287: (EET.UNSIGNED, "DocTypeVersion"), |
||||||
|
0x4285: (EET.UNSIGNED, "DocTypeReadVersion"), |
||||||
|
0xEC: (EET.BINARY, "Void"), |
||||||
|
0xBF: (EET.BINARY, "CRC-32"), |
||||||
|
0x1B538667: (EET.MASTER, "SignatureSlot"), |
||||||
|
0x7E8A: (EET.UNSIGNED, "SignatureAlgo"), |
||||||
|
0x7E9A: (EET.UNSIGNED, "SignatureHash"), |
||||||
|
0x7EA5: (EET.BINARY, "SignaturePublicKey"), |
||||||
|
0x7EB5: (EET.BINARY, "Signature"), |
||||||
|
0x7E5B: (EET.MASTER, "SignatureElements"), |
||||||
|
0x7E7B: (EET.MASTER, "SignatureElementList"), |
||||||
|
0x6532: (EET.BINARY, "SignedElement"), |
||||||
|
0x18538067: (EET.JUST_GO_ON, "Segment"), |
||||||
|
0x114D9B74: (EET.MASTER, "SeekHead"), |
||||||
|
0x4DBB: (EET.MASTER, "Seek"), |
||||||
|
0x53AB: (EET.BINARY, "SeekID"), |
||||||
|
0x53AC: (EET.UNSIGNED, "SeekPosition"), |
||||||
|
0x1549A966: (EET.MASTER, "Info"), |
||||||
|
0x73A4: (EET.BINARY, "SegmentUID"), |
||||||
|
0x7384: (EET.TEXTU, "SegmentFilename"), |
||||||
|
0x3CB923: (EET.BINARY, "PrevUID"), |
||||||
|
0x3C83AB: (EET.TEXTU, "PrevFilename"), |
||||||
|
0x3EB923: (EET.BINARY, "NextUID"), |
||||||
|
0x3E83BB: (EET.TEXTU, "NextFilename"), |
||||||
|
0x4444: (EET.BINARY, "SegmentFamily"), |
||||||
|
0x6924: (EET.MASTER, "ChapterTranslate"), |
||||||
|
0x69FC: (EET.UNSIGNED, "ChapterTranslateEditionUID"), |
||||||
|
0x69BF: (EET.UNSIGNED, "ChapterTranslateCodec"), |
||||||
|
0x69A5: (EET.BINARY, "ChapterTranslateID"), |
||||||
|
0x2AD7B1: (EET.UNSIGNED, "TimecodeScale"), |
||||||
|
0x4489: (EET.FLOAT, "Duration"), |
||||||
|
0x4461: (EET.DATE, "DateUTC"), |
||||||
|
0x7BA9: (EET.TEXTU, "Title"), |
||||||
|
0x4D80: (EET.TEXTU, "MuxingApp"), |
||||||
|
0x5741: (EET.TEXTU, "WritingApp"), |
||||||
|
0x1F43B675: (EET.JUST_GO_ON, "Cluster"), |
||||||
|
0xE7: (EET.UNSIGNED, "Timecode"), |
||||||
|
0x5854: (EET.MASTER, "SilentTracks"), |
||||||
|
0x58D7: (EET.UNSIGNED, "SilentTrackNumber"), |
||||||
|
0xA7: (EET.UNSIGNED, "Position"), |
||||||
|
0xAB: (EET.UNSIGNED, "PrevSize"), |
||||||
|
0xA3: (EET.BINARY, "SimpleBlock"), |
||||||
|
0xA0: (EET.MASTER, "BlockGroup"), |
||||||
|
0xA1: (EET.BINARY, "Block"), |
||||||
|
0xA2: (EET.BINARY, "BlockVirtual"), |
||||||
|
0x75A1: (EET.MASTER, "BlockAdditions"), |
||||||
|
0xA6: (EET.MASTER, "BlockMore"), |
||||||
|
0xEE: (EET.UNSIGNED, "BlockAddID"), |
||||||
|
0xA5: (EET.BINARY, "BlockAdditional"), |
||||||
|
0x9B: (EET.UNSIGNED, "BlockDuration"), |
||||||
|
0xFA: (EET.UNSIGNED, "ReferencePriority"), |
||||||
|
0xFB: (EET.SIGNED, "ReferenceBlock"), |
||||||
|
0xFD: (EET.SIGNED, "ReferenceVirtual"), |
||||||
|
0xA4: (EET.BINARY, "CodecState"), |
||||||
|
0x8E: (EET.MASTER, "Slices"), |
||||||
|
0xE8: (EET.MASTER, "TimeSlice"), |
||||||
|
0xCC: (EET.UNSIGNED, "LaceNumber"), |
||||||
|
0xCD: (EET.UNSIGNED, "FrameNumber"), |
||||||
|
0xCB: (EET.UNSIGNED, "BlockAdditionID"), |
||||||
|
0xCE: (EET.UNSIGNED, "Delay"), |
||||||
|
0xCF: (EET.UNSIGNED, "SliceDuration"), |
||||||
|
0xC8: (EET.MASTER, "ReferenceFrame"), |
||||||
|
0xC9: (EET.UNSIGNED, "ReferenceOffset"), |
||||||
|
0xCA: (EET.UNSIGNED, "ReferenceTimeCode"), |
||||||
|
0xAF: (EET.BINARY, "EncryptedBlock"), |
||||||
|
0x1654AE6B: (EET.MASTER, "Tracks"), |
||||||
|
0xAE: (EET.MASTER, "TrackEntry"), |
||||||
|
0xD7: (EET.UNSIGNED, "TrackNumber"), |
||||||
|
0x73C5: (EET.UNSIGNED, "TrackUID"), |
||||||
|
0x83: (EET.UNSIGNED, "TrackType"), |
||||||
|
0xB9: (EET.UNSIGNED, "FlagEnabled"), |
||||||
|
0x88: (EET.UNSIGNED, "FlagDefault"), |
||||||
|
0x55AA: (EET.UNSIGNED, "FlagForced"), |
||||||
|
0x9C: (EET.UNSIGNED, "FlagLacing"), |
||||||
|
0x6DE7: (EET.UNSIGNED, "MinCache"), |
||||||
|
0x6DF8: (EET.UNSIGNED, "MaxCache"), |
||||||
|
0x23E383: (EET.UNSIGNED, "DefaultDuration"), |
||||||
|
0x23314F: (EET.FLOAT, "TrackTimecodeScale"), |
||||||
|
0x537F: (EET.SIGNED, "TrackOffset"), |
||||||
|
0x55EE: (EET.UNSIGNED, "MaxBlockAdditionID"), |
||||||
|
0x536E: (EET.TEXTU, "Name"), |
||||||
|
0x22B59C: (EET.TEXTA, "Language"), |
||||||
|
0x86: (EET.TEXTA, "CodecID"), |
||||||
|
0x63A2: (EET.BINARY, "CodecPrivate"), |
||||||
|
0x258688: (EET.TEXTU, "CodecName"), |
||||||
|
0x7446: (EET.UNSIGNED, "AttachmentLink"), |
||||||
|
0x3A9697: (EET.TEXTU, "CodecSettings"), |
||||||
|
0x3B4040: (EET.TEXTA, "CodecInfoURL"), |
||||||
|
0x26B240: (EET.TEXTA, "CodecDownloadURL"), |
||||||
|
0xAA: (EET.UNSIGNED, "CodecDecodeAll"), |
||||||
|
0x6FAB: (EET.UNSIGNED, "TrackOverlay"), |
||||||
|
0x6624: (EET.MASTER, "TrackTranslate"), |
||||||
|
0x66FC: (EET.UNSIGNED, "TrackTranslateEditionUID"), |
||||||
|
0x66BF: (EET.UNSIGNED, "TrackTranslateCodec"), |
||||||
|
0x66A5: (EET.BINARY, "TrackTranslateTrackID"), |
||||||
|
0xE0: (EET.MASTER, "Video"), |
||||||
|
0x9A: (EET.UNSIGNED, "FlagInterlaced"), |
||||||
|
0x53B8: (EET.UNSIGNED, "StereoMode"), |
||||||
|
0x53B9: (EET.UNSIGNED, "OldStereoMode"), |
||||||
|
0xB0: (EET.UNSIGNED, "PixelWidth"), |
||||||
|
0xBA: (EET.UNSIGNED, "PixelHeight"), |
||||||
|
0x54AA: (EET.UNSIGNED, "PixelCropBottom"), |
||||||
|
0x54BB: (EET.UNSIGNED, "PixelCropTop"), |
||||||
|
0x54CC: (EET.UNSIGNED, "PixelCropLeft"), |
||||||
|
0x54DD: (EET.UNSIGNED, "PixelCropRight"), |
||||||
|
0x54B0: (EET.UNSIGNED, "DisplayWidth"), |
||||||
|
0x54BA: (EET.UNSIGNED, "DisplayHeight"), |
||||||
|
0x54B2: (EET.UNSIGNED, "DisplayUnit"), |
||||||
|
0x54B3: (EET.UNSIGNED, "AspectRatioType"), |
||||||
|
0x2EB524: (EET.BINARY, "ColourSpace"), |
||||||
|
0x2FB523: (EET.FLOAT, "GammaValue"), |
||||||
|
0x2383E3: (EET.FLOAT, "FrameRate"), |
||||||
|
0xE1: (EET.MASTER, "Audio"), |
||||||
|
0xB5: (EET.FLOAT, "SamplingFrequency"), |
||||||
|
0x78B5: (EET.FLOAT, "OutputSamplingFrequency"), |
||||||
|
0x9F: (EET.UNSIGNED, "Channels"), |
||||||
|
0x7D7B: (EET.BINARY, "ChannelPositions"), |
||||||
|
0x6264: (EET.UNSIGNED, "BitDepth"), |
||||||
|
0xE2: (EET.MASTER, "TrackOperation"), |
||||||
|
0xE3: (EET.MASTER, "TrackCombinePlanes"), |
||||||
|
0xE4: (EET.MASTER, "TrackPlane"), |
||||||
|
0xE5: (EET.UNSIGNED, "TrackPlaneUID"), |
||||||
|
0xE6: (EET.UNSIGNED, "TrackPlaneType"), |
||||||
|
0xE9: (EET.MASTER, "TrackJoinBlocks"), |
||||||
|
0xED: (EET.UNSIGNED, "TrackJoinUID"), |
||||||
|
0xC0: (EET.UNSIGNED, "TrickTrackUID"), |
||||||
|
0xC1: (EET.BINARY, "TrickTrackSegmentUID"), |
||||||
|
0xC6: (EET.UNSIGNED, "TrickTrackFlag"), |
||||||
|
0xC7: (EET.UNSIGNED, "TrickMasterTrackUID"), |
||||||
|
0xC4: (EET.BINARY, "TrickMasterTrackSegmentUID"), |
||||||
|
0x6D80: (EET.MASTER, "ContentEncodings"), |
||||||
|
0x6240: (EET.MASTER, "ContentEncoding"), |
||||||
|
0x5031: (EET.UNSIGNED, "ContentEncodingOrder"), |
||||||
|
0x5032: (EET.UNSIGNED, "ContentEncodingScope"), |
||||||
|
0x5033: (EET.UNSIGNED, "ContentEncodingType"), |
||||||
|
0x5034: (EET.MASTER, "ContentCompression"), |
||||||
|
0x4254: (EET.UNSIGNED, "ContentCompAlgo"), |
||||||
|
0x4255: (EET.BINARY, "ContentCompSettings"), |
||||||
|
0x5035: (EET.MASTER, "ContentEncryption"), |
||||||
|
0x47E1: (EET.UNSIGNED, "ContentEncAlgo"), |
||||||
|
0x47E2: (EET.BINARY, "ContentEncKeyID"), |
||||||
|
0x47E3: (EET.BINARY, "ContentSignature"), |
||||||
|
0x47E4: (EET.BINARY, "ContentSigKeyID"), |
||||||
|
0x47E5: (EET.UNSIGNED, "ContentSigAlgo"), |
||||||
|
0x47E6: (EET.UNSIGNED, "ContentSigHashAlgo"), |
||||||
|
0x1C53BB6B: (EET.MASTER, "Cues"), |
||||||
|
0xBB: (EET.MASTER, "CuePoint"), |
||||||
|
0xB3: (EET.UNSIGNED, "CueTime"), |
||||||
|
0xB7: (EET.MASTER, "CueTrackPositions"), |
||||||
|
0xF7: (EET.UNSIGNED, "CueTrack"), |
||||||
|
0xF1: (EET.UNSIGNED, "CueClusterPosition"), |
||||||
|
0x5378: (EET.UNSIGNED, "CueBlockNumber"), |
||||||
|
0xEA: (EET.UNSIGNED, "CueCodecState"), |
||||||
|
0xDB: (EET.MASTER, "CueReference"), |
||||||
|
0x96: (EET.UNSIGNED, "CueRefTime"), |
||||||
|
0x97: (EET.UNSIGNED, "CueRefCluster"), |
||||||
|
0x535F: (EET.UNSIGNED, "CueRefNumber"), |
||||||
|
0xEB: (EET.UNSIGNED, "CueRefCodecState"), |
||||||
|
0x1941A469: (EET.MASTER, "Attachments"), |
||||||
|
0x61A7: (EET.MASTER, "AttachedFile"), |
||||||
|
0x467E: (EET.TEXTU, "FileDescription"), |
||||||
|
0x466E: (EET.TEXTU, "FileName"), |
||||||
|
0x4660: (EET.TEXTA, "FileMimeType"), |
||||||
|
0x465C: (EET.BINARY, "FileData"), |
||||||
|
0x46AE: (EET.UNSIGNED, "FileUID"), |
||||||
|
0x4675: (EET.BINARY, "FileReferral"), |
||||||
|
0x4661: (EET.UNSIGNED, "FileUsedStartTime"), |
||||||
|
0x4662: (EET.UNSIGNED, "FileUsedEndTime"), |
||||||
|
0x1043A770: (EET.MASTER, "Chapters"), |
||||||
|
0x45B9: (EET.MASTER, "EditionEntry"), |
||||||
|
0x45BC: (EET.UNSIGNED, "EditionUID"), |
||||||
|
0x45BD: (EET.UNSIGNED, "EditionFlagHidden"), |
||||||
|
0x45DB: (EET.UNSIGNED, "EditionFlagDefault"), |
||||||
|
0x45DD: (EET.UNSIGNED, "EditionFlagOrdered"), |
||||||
|
0xB6: (EET.MASTER, "ChapterAtom"), |
||||||
|
0x73C4: (EET.UNSIGNED, "ChapterUID"), |
||||||
|
0x91: (EET.UNSIGNED, "ChapterTimeStart"), |
||||||
|
0x92: (EET.UNSIGNED, "ChapterTimeEnd"), |
||||||
|
0x98: (EET.UNSIGNED, "ChapterFlagHidden"), |
||||||
|
0x4598: (EET.UNSIGNED, "ChapterFlagEnabled"), |
||||||
|
0x6E67: (EET.BINARY, "ChapterSegmentUID"), |
||||||
|
0x6EBC: (EET.UNSIGNED, "ChapterSegmentEditionUID"), |
||||||
|
0x63C3: (EET.UNSIGNED, "ChapterPhysicalEquiv"), |
||||||
|
0x8F: (EET.MASTER, "ChapterTrack"), |
||||||
|
0x89: (EET.UNSIGNED, "ChapterTrackNumber"), |
||||||
|
0x80: (EET.MASTER, "ChapterDisplay"), |
||||||
|
0x85: (EET.TEXTU, "ChapString"), |
||||||
|
0x437C: (EET.TEXTA, "ChapLanguage"), |
||||||
|
0x437E: (EET.TEXTA, "ChapCountry"), |
||||||
|
0x6944: (EET.MASTER, "ChapProcess"), |
||||||
|
0x6955: (EET.UNSIGNED, "ChapProcessCodecID"), |
||||||
|
0x450D: (EET.BINARY, "ChapProcessPrivate"), |
||||||
|
0x6911: (EET.MASTER, "ChapProcessCommand"), |
||||||
|
0x6922: (EET.UNSIGNED, "ChapProcessTime"), |
||||||
|
0x6933: (EET.BINARY, "ChapProcessData"), |
||||||
|
0x1254C367: (EET.MASTER, "Tags"), |
||||||
|
0x7373: (EET.MASTER, "Tag"), |
||||||
|
0x63C0: (EET.MASTER, "Targets"), |
||||||
|
0x68CA: (EET.UNSIGNED, "TargetTypeValue"), |
||||||
|
0x63CA: (EET.TEXTA, "TargetType"), |
||||||
|
0x63C5: (EET.UNSIGNED, "TagTrackUID"), |
||||||
|
0x63C9: (EET.UNSIGNED, "TagEditionUID"), |
||||||
|
0x63C4: (EET.UNSIGNED, "TagChapterUID"), |
||||||
|
0x63C6: (EET.UNSIGNED, "TagAttachmentUID"), |
||||||
|
0x67C8: (EET.MASTER, "SimpleTag"), |
||||||
|
0x45A3: (EET.TEXTU, "TagName"), |
||||||
|
0x447A: (EET.TEXTA, "TagLanguage"), |
||||||
|
0x4484: (EET.UNSIGNED, "TagDefault"), |
||||||
|
0x4487: (EET.TEXTU, "TagString"), |
||||||
|
0x4485: (EET.BINARY, "TagBinary"), |
||||||
|
0x56AA: (EET.UNSIGNED, "CodecDelay"), |
||||||
|
0x56BB: (EET.UNSIGNED, "SeekPreRoll"), |
||||||
|
0xF0: (EET.UNSIGNED, "CueRelativePosition"), |
||||||
|
0x53C0: (EET.UNSIGNED, "AlphaMode"), |
||||||
|
0x55B2: (EET.UNSIGNED, "BitsPerChannel"), |
||||||
|
0x55B5: (EET.UNSIGNED, "CbSubsamplingHorz"), |
||||||
|
0x55B6: (EET.UNSIGNED, "CbSubsamplingVert"), |
||||||
|
0x5654: (EET.TEXTU, "ChapterStringUID"), |
||||||
|
0x55B7: (EET.UNSIGNED, "ChromaSitingHorz"), |
||||||
|
0x55B8: (EET.UNSIGNED, "ChromaSitingVert"), |
||||||
|
0x55B3: (EET.UNSIGNED, "ChromaSubsamplingHorz"), |
||||||
|
0x55B4: (EET.UNSIGNED, "ChromaSubsamplingVert"), |
||||||
|
0x55B0: (EET.MASTER, "Colour"), |
||||||
|
0x234E7A: (EET.UNSIGNED, "DefaultDecodedFieldDuration"), |
||||||
|
0x75A2: (EET.SIGNED, "DiscardPadding"), |
||||||
|
0x9D: (EET.UNSIGNED, "FieldOrder"), |
||||||
|
0x55D9: (EET.FLOAT, "LuminanceMax"), |
||||||
|
0x55DA: (EET.FLOAT, "LuminanceMin"), |
||||||
|
0x55D0: (EET.MASTER, "MasteringMetadata"), |
||||||
|
0x55B1: (EET.UNSIGNED, "MatrixCoefficients"), |
||||||
|
0x55BC: (EET.UNSIGNED, "MaxCLL"), |
||||||
|
0x55BD: (EET.UNSIGNED, "MaxFALL"), |
||||||
|
0x55BB: (EET.UNSIGNED, "Primaries"), |
||||||
|
0x55D5: (EET.FLOAT, "PrimaryBChromaticityX"), |
||||||
|
0x55D6: (EET.FLOAT, "PrimaryBChromaticityY"), |
||||||
|
0x55D3: (EET.FLOAT, "PrimaryGChromaticityX"), |
||||||
|
0x55D4: (EET.FLOAT, "PrimaryGChromaticityY"), |
||||||
|
0x55D1: (EET.FLOAT, "PrimaryRChromaticityX"), |
||||||
|
0x55D2: (EET.FLOAT, "PrimaryRChromaticityY"), |
||||||
|
0x55B9: (EET.UNSIGNED, "Range"), |
||||||
|
0x55BA: (EET.UNSIGNED, "TransferCharacteristics"), |
||||||
|
0x55D7: (EET.FLOAT, "WhitePointChromaticityX"), |
||||||
|
0x55D8: (EET.FLOAT, "WhitePointChromaticityY"), |
||||||
|
} |
||||||
|
|
||||||
|
def read_simple_element(f, type_, size): |
||||||
|
date = None |
||||||
|
if size==0: |
||||||
|
return "" |
||||||
|
|
||||||
|
if type_==EET.UNSIGNED: |
||||||
|
data=read_fixedlength_number(f, size, False) |
||||||
|
elif type_==EET.SIGNED: |
||||||
|
data=read_fixedlength_number(f, size, True) |
||||||
|
elif type_==EET.TEXTA: |
||||||
|
data=f.read(size) |
||||||
|
data = data.replace(b"\x00", b"") # filter out \0, for gstreamer |
||||||
|
data = data.decode("ascii") |
||||||
|
elif type_==EET.TEXTU: |
||||||
|
data=f.read(size) |
||||||
|
data = data.replace(b"\x00", b"") # filter out \0, for gstreamer |
||||||
|
data = data.decode("UTF-8") |
||||||
|
elif type_==EET.MASTER: |
||||||
|
data=read_ebml_element_tree(f, size) |
||||||
|
elif type_==EET.DATE: |
||||||
|
data=read_fixedlength_number(f, size, True) |
||||||
|
data*= 1e-9 |
||||||
|
data+= (datetime.datetime(2001, 1, 1) - datetime.datetime(1970, 1, 1)).total_seconds() |
||||||
|
# now should be UNIX date |
||||||
|
elif type_==EET.FLOAT: |
||||||
|
if size==4: |
||||||
|
data = f.read(4) |
||||||
|
data = unpack(">f", data)[0] |
||||||
|
elif size==8: |
||||||
|
data = f.read(8) |
||||||
|
data = unpack(">d", data)[0] |
||||||
|
else: |
||||||
|
data=read_fixedlength_number(f, size, False) |
||||||
|
sys.stderr.write("mkvparse: Floating point of size %d is not supported\n" % size) |
||||||
|
data = None |
||||||
|
else: |
||||||
|
data=f.read(size) |
||||||
|
return data |
||||||
|
|
||||||
|
def read_ebml_element_tree(f, total_size): |
||||||
|
''' |
||||||
|
Build tree of elements, reading f until total_size reached |
||||||
|
Don't use for the whole segment, it's not Haskell |
||||||
|
|
||||||
|
Returns list of pairs (element_name, element_value). |
||||||
|
element_value can also be list of pairs |
||||||
|
''' |
||||||
|
childs=[] |
||||||
|
while(total_size>0): |
||||||
|
(id_, size, hsize) = read_ebml_element_header(f) |
||||||
|
if size == -1: |
||||||
|
sys.stderr.write("mkvparse: Element %x without size? Damaged data? Skipping %d bytes\n" % (id_, size, total_size)) |
||||||
|
f.read(total_size); |
||||||
|
break; |
||||||
|
if size>total_size: |
||||||
|
sys.stderr.write("mkvparse: Element %x with size %d? Damaged data? Skipping %d bytes\n" % (id_, size, total_size)) |
||||||
|
f.read(total_size); |
||||||
|
break |
||||||
|
type_ = EET.BINARY |
||||||
|
name = "unknown_%x"%id_ |
||||||
|
if id_ in element_types_names: |
||||||
|
(type_, name) = element_types_names[id_] |
||||||
|
data = read_simple_element(f, type_, size) |
||||||
|
total_size-=(size+hsize) |
||||||
|
childs.append((name, (type_, data))) |
||||||
|
return childs |
||||||
|
|
||||||
|
|
||||||
|
class MatroskaHandler: |
||||||
|
""" User for mkvparse should override these methods """ |
||||||
|
def tracks_available(self): |
||||||
|
pass |
||||||
|
def segment_info_available(self): |
||||||
|
pass |
||||||
|
def frame(self, track_id, timestamp, data, more_laced_frames, duration, keyframe, invisible, discardable): |
||||||
|
pass |
||||||
|
def ebml_top_element(self, id_, name_, type_, data_): |
||||||
|
pass |
||||||
|
def before_handling_an_element(self): |
||||||
|
pass |
||||||
|
def begin_handling_ebml_element(self, id_, name, type_, headersize, datasize): |
||||||
|
return type_ |
||||||
|
def element_data_available(self, id_, name, type_, headersize, data): |
||||||
|
pass |
||||||
|
|
||||||
|
def handle_block(buffer, buffer_pos, handler, cluster_timecode, timecode_scale=1000000, duration=None, header_removal_headers_for_tracks={}): |
||||||
|
''' |
||||||
|
Decode a block, handling all lacings, send it to handler with appropriate timestamp, track number |
||||||
|
''' |
||||||
|
pos=0 |
||||||
|
(tracknum, pos) = parse_matroska_number(buffer, pos, signed=False) |
||||||
|
(tcode, pos) = parse_fixedlength_number(buffer, pos, 2, signed=True) |
||||||
|
flags = ord(buffer[pos]); pos+=1 |
||||||
|
f_keyframe = (flags&0x80 == 0x80) |
||||||
|
f_invisible = (flags&0x08 == 0x08) |
||||||
|
f_discardable = (flags&0x01 == 0x01) |
||||||
|
laceflags=flags&0x06 |
||||||
|
|
||||||
|
block_timecode = (cluster_timecode + tcode)*(timecode_scale*0.000000001) |
||||||
|
|
||||||
|
header_removal_prefix = b"" |
||||||
|
if tracknum in header_removal_headers_for_tracks: |
||||||
|
# header_removal_prefix = header_removal_headers_for_tracks[tracknum] |
||||||
|
raise NotImplementedError |
||||||
|
|
||||||
|
if laceflags == 0x00: # no lacing |
||||||
|
# buf = buffer[pos:] |
||||||
|
handler.frame(tracknum, block_timecode, buffer_pos+pos, len(buffer)-pos, |
||||||
|
0, duration, f_keyframe, f_invisible, f_discardable) |
||||||
|
return |
||||||
|
|
||||||
|
numframes = ord(buffer[pos]); pos+=1 |
||||||
|
numframes+=1 |
||||||
|
|
||||||
|
lengths=[] |
||||||
|
|
||||||
|
if laceflags == 0x02: # Xiph lacing |
||||||
|
accumlength=0 |
||||||
|
for i in range(numframes-1): |
||||||
|
(l, pos) = parse_xiph_number(buffer, pos) |
||||||
|
lengths.append(l) |
||||||
|
accumlength+=l |
||||||
|
lengths.append(len(buffer)-pos-accumlength) |
||||||
|
elif laceflags == 0x06: # EBML lacing |
||||||
|
accumlength=0 |
||||||
|
if numframes: |
||||||
|
(flength, pos) = parse_matroska_number(buffer, pos, signed=False) |
||||||
|
lengths.append(flength) |
||||||
|
accumlength+=flength |
||||||
|
for i in range(numframes-2): |
||||||
|
(l, pos) = parse_matroska_number(buffer, pos, signed=True) |
||||||
|
flength+=l |
||||||
|
lengths.append(flength) |
||||||
|
accumlength+=flength |
||||||
|
lengths.append(len(buffer)-pos-accumlength) |
||||||
|
elif laceflags==0x04: # Fixed size lacing |
||||||
|
fl=int((len(buffer)-pos)/numframes) |
||||||
|
for i in range(numframes): |
||||||
|
lengths.append(fl) |
||||||
|
|
||||||
|
more_laced_frames=numframes-1 |
||||||
|
for i in lengths: |
||||||
|
# buf = buffer[pos:pos+i] |
||||||
|
handler.frame(tracknum, block_timecode, buffer_pos+pos, i, more_laced_frames, duration, |
||||||
|
f_keyframe, f_invisible, f_discardable) |
||||||
|
pos+=i |
||||||
|
more_laced_frames-=1 |
||||||
|
|
||||||
|
|
||||||
|
def resync(f): |
||||||
|
sys.stderr.write("mvkparse: Resyncing\n") |
||||||
|
while True: |
||||||
|
b = f.read(1); |
||||||
|
if b == b"": return (None, None); |
||||||
|
if b == b"\x1F": |
||||||
|
b2 = f.read(3); |
||||||
|
if b2 == b"\x43\xB6\x75": |
||||||
|
(seglen, x) = read_matroska_number(f) |
||||||
|
return (0x1F43B675, seglen, x+4) # cluster |
||||||
|
if b == b"\x18": |
||||||
|
b2 = f.read(3) |
||||||
|
if b2 == b"\x53\x80\x67": |
||||||
|
(seglen, x) = read_matroska_number(f) |
||||||
|
return (0x18538067, seglen, x+4) # segment |
||||||
|
if b == b"\x16": |
||||||
|
b2 = f.read(3) |
||||||
|
if b2 == b"\x54\xAE\x6B": |
||||||
|
(seglen ,x )= read_matroska_number(f) |
||||||
|
return (0x1654AE6B, seglen, x+4) # tracks |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def mkvparse(f, handler): |
||||||
|
''' |
||||||
|
Read mkv file f and call handler methods when track or segment information is ready or when frame is read. |
||||||
|
Handles lacing, timecodes (except of per-track scaling) |
||||||
|
''' |
||||||
|
timecode_scale = 1000000 |
||||||
|
current_cluster_timecode = 0 |
||||||
|
resync_element_id = None |
||||||
|
resync_element_size = None |
||||||
|
resync_element_headersize = None |
||||||
|
header_removal_headers_for_tracks = {} |
||||||
|
while f: |
||||||
|
(id_, size, hsize) = (None, None, None) |
||||||
|
tree = None |
||||||
|
data = None |
||||||
|
(type_, name) = (None, None) |
||||||
|
try: |
||||||
|
if not resync_element_id: |
||||||
|
try: |
||||||
|
handler.before_handling_an_element() |
||||||
|
(id_, size, hsize) = read_ebml_element_header(f) |
||||||
|
except StopIteration: |
||||||
|
break; |
||||||
|
if not (id_ in element_types_names): |
||||||
|
sys.stderr.write("mkvparse: Unknown element with id %x and size %d\n"%(id_, size)) |
||||||
|
(resync_element_id, resync_element_size, resync_element_headersize) = resync(f) |
||||||
|
if resync_element_id: |
||||||
|
continue; |
||||||
|
else: |
||||||
|
break; |
||||||
|
else: |
||||||
|
id_ = resync_element_id |
||||||
|
size=resync_element_size |
||||||
|
hsize=resync_element_headersize |
||||||
|
resync_element_id = None |
||||||
|
resync_element_size = None |
||||||
|
resync_element_headersize = None |
||||||
|
|
||||||
|
(type_, name) = element_types_names[id_] |
||||||
|
(type_, name) = element_types_names[id_] |
||||||
|
type_ = handler.begin_handling_ebml_element(id_, name, type_, hsize, size) |
||||||
|
|
||||||
|
if type_ == EET.MASTER: |
||||||
|
tree = read_ebml_element_tree(f, size) |
||||||
|
data = tree |
||||||
|
|
||||||
|
except Exception: |
||||||
|
traceback.print_exc() |
||||||
|
handler.before_handling_an_element() |
||||||
|
(resync_element_id, resync_element_size, resync_element_headersize) = resync(f) |
||||||
|
if resync_element_id: |
||||||
|
continue; |
||||||
|
else: |
||||||
|
break; |
||||||
|
|
||||||
|
if name=="EBML" and type(data) == list: |
||||||
|
d = dict(tree) |
||||||
|
if 'EBMLReadVersion' in d: |
||||||
|
if d['EBMLReadVersion'][1]>1: sys.stderr.write("mkvparse: Warning: EBMLReadVersion too big\n") |
||||||
|
if 'DocTypeReadVersion' in d: |
||||||
|
if d['DocTypeReadVersion'][1]>2: sys.stderr.write("mkvparse: Warning: DocTypeReadVersion too big\n") |
||||||
|
dt = d['DocType'][1] |
||||||
|
if dt != "matroska" and dt != "webm": |
||||||
|
sys.stderr.write("mkvparse: Warning: EBML DocType is not \"matroska\" or \"webm\"") |
||||||
|
elif name=="Info" and type(data) == list: |
||||||
|
handler.segment_info = tree |
||||||
|
handler.segment_info_available() |
||||||
|
|
||||||
|
d = dict(tree) |
||||||
|
if "TimecodeScale" in d: |
||||||
|
timecode_scale = d["TimecodeScale"][1] |
||||||
|
elif name=="Tracks" and type(data) == list: |
||||||
|
handler.tracks={} |
||||||
|
for (ten, (_t, track)) in tree: |
||||||
|
if ten != "TrackEntry": continue |
||||||
|
d = dict(track) |
||||||
|
n = d['TrackNumber'][1] |
||||||
|
handler.tracks[n]=d |
||||||
|
tt = d['TrackType'][1] |
||||||
|
if tt==0x01: d['type']='video' |
||||||
|
elif tt==0x02: d['type']='audio' |
||||||
|
elif tt==0x03: d['type']='complex' |
||||||
|
elif tt==0x10: d['type']='logo' |
||||||
|
elif tt==0x11: d['type']='subtitle' |
||||||
|
elif tt==0x12: d['type']='button' |
||||||
|
elif tt==0x20: d['type']='control' |
||||||
|
if 'TrackTimecodeScale' in d: |
||||||
|
sys.stderr.write("mkvparse: Warning: TrackTimecodeScale is not supported\n") |
||||||
|
if 'ContentEncodings' in d: |
||||||
|
try: |
||||||
|
compr = dict(d["ContentEncodings"][1][0][1][1][0][1][1]) |
||||||
|
if compr["ContentCompAlgo"][1] == 3: |
||||||
|
header_removal_headers_for_tracks[n] = compr["ContentCompSettings"][1] |
||||||
|
else: |
||||||
|
sys.stderr.write("mkvparse: Warning: compression other than " \ |
||||||
|
"header removal is not supported\n") |
||||||
|
except: |
||||||
|
sys.stderr.write("mkvparse: Warning: unsuccessfully tried " \ |
||||||
|
"to handle header removal compression\n") |
||||||
|
handler.tracks_available() |
||||||
|
# cluster contents: |
||||||
|
elif name=="Timecode" and type_ == EET.UNSIGNED: |
||||||
|
data=read_fixedlength_number(f, size, False) |
||||||
|
current_cluster_timecode = data; |
||||||
|
elif name=="SimpleBlock" and type_ == EET.BINARY: |
||||||
|
pos = f.tell() |
||||||
|
data=f.read(size) |
||||||
|
handle_block(data, pos, handler, current_cluster_timecode, timecode_scale, None, header_removal_headers_for_tracks) |
||||||
|
elif name=="BlockGroup" and type_ == EET.MASTER: |
||||||
|
d2 = dict(tree) |
||||||
|
duration=None |
||||||
|
raise NotImplementedError |
||||||
|
# if 'BlockDuration' in d2: |
||||||
|
# duration = d2['BlockDuration'][1] |
||||||
|
# duration = duration*0.000000001*timecode_scale |
||||||
|
# if 'Block' in d2: |
||||||
|
# handle_block(d2['Block'][1], None, handler, current_cluster_timecode, timecode_scale, duration, header_removal_headers_for_tracks) |
||||||
|
else: |
||||||
|
if type_!=EET.JUST_GO_ON and type_!=EET.MASTER: |
||||||
|
data = read_simple_element(f, type_, size) |
||||||
|
|
||||||
|
handler.ebml_top_element(id_, name, type_, data); |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__': |
||||||
|
print("Run mkvuser.py for the example") |
@ -0,0 +1,107 @@ |
|||||||
|
import os |
||||||
|
import select |
||||||
|
import fcntl |
||||||
|
from itertools import count |
||||||
|
from collections import deque |
||||||
|
|
||||||
|
Empty = OSError |
||||||
|
Full = OSError |
||||||
|
ExistentialError = OSError |
||||||
|
|
||||||
|
class PollableQueue(object): |
||||||
|
"""A Queue that you can poll(). |
||||||
|
Only works with a single producer. |
||||||
|
""" |
||||||
|
def __init__(self, maxlen=None): |
||||||
|
with open("/proc/sys/fs/pipe-max-size") as f: |
||||||
|
max_maxlen = int(f.read().rstrip()) |
||||||
|
|
||||||
|
if maxlen is None: |
||||||
|
maxlen = max_maxlen |
||||||
|
else: |
||||||
|
maxlen = min(maxlen, max_maxlen) |
||||||
|
|
||||||
|
self._maxlen = maxlen |
||||||
|
self._q = deque() |
||||||
|
self._get_fd, self._put_fd = os.pipe() |
||||||
|
fcntl.fcntl(self._get_fd, fcntl.F_SETFL, os.O_NONBLOCK) |
||||||
|
fcntl.fcntl(self._put_fd, fcntl.F_SETFL, os.O_NONBLOCK) |
||||||
|
|
||||||
|
fcntl.fcntl(self._get_fd, fcntl.F_SETLEASE + 7, self._maxlen) |
||||||
|
fcntl.fcntl(self._put_fd, fcntl.F_SETLEASE + 7, self._maxlen) |
||||||
|
|
||||||
|
get_poller = select.epoll() |
||||||
|
put_poller = select.epoll() |
||||||
|
get_poller.register(self._get_fd, select.EPOLLIN) |
||||||
|
put_poller.register(self._put_fd, select.EPOLLOUT) |
||||||
|
|
||||||
|
self._get_poll = get_poller.poll |
||||||
|
self._put_poll = put_poller.poll |
||||||
|
|
||||||
|
|
||||||
|
def get_fd(self): |
||||||
|
return self._get_fd |
||||||
|
|
||||||
|
def put_fd(self): |
||||||
|
return self._put_fd |
||||||
|
|
||||||
|
def put(self, item, block=True, timeout=None): |
||||||
|
if block: |
||||||
|
while self._put_poll(timeout if timeout is not None else -1): |
||||||
|
try: |
||||||
|
# TODO: This is broken for multiple push threads when the queue is full. |
||||||
|
return self.put_nowait(item) |
||||||
|
except OSError as e: |
||||||
|
if e.errno != 11: |
||||||
|
raise |
||||||
|
|
||||||
|
raise Full() |
||||||
|
else: |
||||||
|
return self.put_nowait(item) |
||||||
|
|
||||||
|
def put_nowait(self, item): |
||||||
|
self._q.appendleft(item) |
||||||
|
os.write(self._put_fd, b"\x00") |
||||||
|
|
||||||
|
def get(self, block=True, timeout=None): |
||||||
|
if block: |
||||||
|
while self._get_poll(timeout if timeout is not None else -1): |
||||||
|
try: |
||||||
|
return self.get_nowait() |
||||||
|
except OSError as e: |
||||||
|
if e.errno != 11: |
||||||
|
raise |
||||||
|
|
||||||
|
raise Empty() |
||||||
|
else: |
||||||
|
return self.get_nowait() |
||||||
|
|
||||||
|
def get_nowait(self): |
||||||
|
os.read(self._get_fd, 1) |
||||||
|
return self._q.pop() |
||||||
|
|
||||||
|
def get_multiple(self, block=True, timeout=None): |
||||||
|
if block: |
||||||
|
if self._get_poll(timeout if timeout is not None else -1): |
||||||
|
return self.get_multiple_nowait() |
||||||
|
else: |
||||||
|
raise Empty() |
||||||
|
else: |
||||||
|
return self.get_multiple_nowait() |
||||||
|
|
||||||
|
def get_multiple_nowait(self, max_messages=None): |
||||||
|
num_read = len(os.read(self._get_fd, max_messages or self._maxlen)) |
||||||
|
return [self._q.pop() for _ in range(num_read)] |
||||||
|
|
||||||
|
def empty(self): |
||||||
|
return len(self._q) == 0 |
||||||
|
|
||||||
|
def full(self): |
||||||
|
return len(self._q) >= self._maxlen |
||||||
|
|
||||||
|
def close(self): |
||||||
|
os.close(self._get_fd) |
||||||
|
os.close(self._put_fd) |
||||||
|
|
||||||
|
def __len__(self): |
||||||
|
return len(self._q) |
@ -0,0 +1,97 @@ |
|||||||
|
import os |
||||||
|
import re |
||||||
|
from collections import defaultdict |
||||||
|
|
||||||
|
SEGMENT_NAME_RE = r'[a-z0-9]{16}[|_][0-9]{4}-[0-9]{2}-[0-9]{2}--[0-9]{2}-[0-9]{2}-[0-9]{2}--[0-9]+' |
||||||
|
EXPLORER_FILE_RE = r'^({})--([a-z]+\.[a-z0-9]+)$'.format(SEGMENT_NAME_RE) |
||||||
|
OP_SEGMENT_DIR_RE = r'^({})$'.format(SEGMENT_NAME_RE) |
||||||
|
|
||||||
|
LOG_FILENAMES = ['rlog.bz2', 'raw_log.bz2', 'log2.gz', 'ilog.7z'] |
||||||
|
CAMERA_FILENAMES = ['fcamera.hevc', 'video.hevc', 'acamera', 'icamera'] |
||||||
|
|
||||||
|
class Route(object): |
||||||
|
def __init__(self, route_name, data_dir): |
||||||
|
self.route_name = route_name.replace('_', '|') |
||||||
|
self._segments = self._get_segments(data_dir) |
||||||
|
|
||||||
|
@property |
||||||
|
def segments(self): |
||||||
|
return self._segments |
||||||
|
|
||||||
|
def log_paths(self): |
||||||
|
max_seg_number = self._segments[-1].canonical_name.segment_num |
||||||
|
log_path_by_seg_num = {s.canonical_name.segment_num: s.log_path for s in self._segments} |
||||||
|
return [log_path_by_seg_num.get(i, None) for i in range(max_seg_number+1)] |
||||||
|
|
||||||
|
def camera_paths(self): |
||||||
|
max_seg_number = self._segments[-1].canonical_name.segment_num |
||||||
|
camera_path_by_seg_num = {s.canonical_name.segment_num: s.camera_path for s in self._segments} |
||||||
|
return [camera_path_by_seg_num.get(i, None) for i in range(max_seg_number+1)] |
||||||
|
|
||||||
|
def _get_segments(self, data_dir): |
||||||
|
files = os.listdir(data_dir) |
||||||
|
segment_files = defaultdict(list) |
||||||
|
|
||||||
|
for f in files: |
||||||
|
fullpath = os.path.join(data_dir, f) |
||||||
|
explorer_match = re.match(EXPLORER_FILE_RE, f) |
||||||
|
op_match = re.match(OP_SEGMENT_DIR_RE, f) |
||||||
|
|
||||||
|
if explorer_match: |
||||||
|
segment_name, fn = explorer_match.groups() |
||||||
|
if segment_name.replace('_', '|').startswith(self.route_name): |
||||||
|
segment_files[segment_name].append((fullpath, fn)) |
||||||
|
elif op_match and os.path.isdir(fullpath): |
||||||
|
segment_name, = op_match.groups() |
||||||
|
if segment_name.startswith(self.route_name): |
||||||
|
for seg_f in os.listdir(fullpath): |
||||||
|
segment_files[segment_name].append((os.path.join(fullpath, seg_f), seg_f)) |
||||||
|
elif f == self.route_name: |
||||||
|
for seg_num in os.listdir(fullpath): |
||||||
|
if not seg_num.isdigit(): |
||||||
|
continue |
||||||
|
|
||||||
|
segment_name = '{}--{}'.format(self.route_name, seg_num) |
||||||
|
for seg_f in os.listdir(os.path.join(fullpath, seg_num)): |
||||||
|
segment_files[segment_name].append((os.path.join(fullpath, seg_num, seg_f), seg_f)) |
||||||
|
|
||||||
|
segments = [] |
||||||
|
for segment, files in segment_files.items(): |
||||||
|
try: |
||||||
|
log_path = next(path for path, filename in files if filename in LOG_FILENAMES) |
||||||
|
except StopIteration: |
||||||
|
log_path = None |
||||||
|
|
||||||
|
try: |
||||||
|
camera_path = next(path for path, filename in files if filename in CAMERA_FILENAMES) |
||||||
|
except StopIteration: |
||||||
|
camera_path = None |
||||||
|
|
||||||
|
segments.append(RouteSegment(segment, log_path, camera_path)) |
||||||
|
|
||||||
|
if len(segments) == 0: |
||||||
|
raise ValueError('Could not find segments for route {} in data directory {}'.format(self.route_name, data_dir)) |
||||||
|
return sorted(segments, key=lambda seg: seg.canonical_name.segment_num) |
||||||
|
|
||||||
|
class RouteSegment(object): |
||||||
|
def __init__(self, name, log_path, camera_path): |
||||||
|
self._name = RouteSegmentName(name) |
||||||
|
self.log_path = log_path |
||||||
|
self.camera_path = camera_path |
||||||
|
|
||||||
|
@property |
||||||
|
def name(self): return str(self._name) |
||||||
|
|
||||||
|
@property |
||||||
|
def canonical_name(self): return self._name |
||||||
|
|
||||||
|
class RouteSegmentName(object): |
||||||
|
def __init__(self, name_str): |
||||||
|
self._segment_name_str = name_str |
||||||
|
self._route_name_str, num_str = self._segment_name_str.rsplit("--", 1) |
||||||
|
self._num = int(num_str) |
||||||
|
|
||||||
|
@property |
||||||
|
def segment_num(self): return self._num |
||||||
|
|
||||||
|
def __str__(self): return self._segment_name_str |
@ -0,0 +1,86 @@ |
|||||||
|
"""RouteFrameReader indexes and reads frames across routes, by frameId or segment indices.""" |
||||||
|
from tools.lib.framereader import FrameReader |
||||||
|
|
||||||
|
class _FrameReaderDict(dict): |
||||||
|
def __init__(self, camera_paths, cache_paths, framereader_kwargs, *args, **kwargs): |
||||||
|
super(_FrameReaderDict, self).__init__(*args, **kwargs) |
||||||
|
|
||||||
|
if cache_paths is None: |
||||||
|
cache_paths = {} |
||||||
|
if not isinstance(cache_paths, dict): |
||||||
|
cache_paths = { k: v for k, v in enumerate(cache_paths) } |
||||||
|
|
||||||
|
self._camera_paths = camera_paths |
||||||
|
self._cache_paths = cache_paths |
||||||
|
self._framereader_kwargs = framereader_kwargs |
||||||
|
|
||||||
|
def __missing__(self, key): |
||||||
|
if key < len(self._camera_paths) and self._camera_paths[key] is not None: |
||||||
|
frame_reader = FrameReader(self._camera_paths[key], |
||||||
|
self._cache_paths.get(key), **self._framereader_kwargs) |
||||||
|
self[key] = frame_reader |
||||||
|
return frame_reader |
||||||
|
else: |
||||||
|
raise KeyError("Segment index out of bounds: {}".format(key)) |
||||||
|
|
||||||
|
|
||||||
|
class RouteFrameReader(object): |
||||||
|
"""Reads frames across routes and route segments by frameId.""" |
||||||
|
def __init__(self, camera_paths, cache_paths, frame_id_lookup, **kwargs): |
||||||
|
"""Create a route framereader. |
||||||
|
|
||||||
|
Inputs: |
||||||
|
TODO |
||||||
|
|
||||||
|
kwargs: Forwarded to the FrameReader function. If cache_prefix is included, that path |
||||||
|
will also be used for frame position indices. |
||||||
|
""" |
||||||
|
self._first_camera_idx = next(i for i in range(len(camera_paths)) if camera_paths[i] is not None) |
||||||
|
self._frame_readers = _FrameReaderDict(camera_paths, cache_paths, kwargs) |
||||||
|
self._frame_id_lookup = frame_id_lookup |
||||||
|
|
||||||
|
@property |
||||||
|
def w(self): |
||||||
|
"""Width of each frame in pixels.""" |
||||||
|
return self._frame_readers[self._first_camera_idx].w |
||||||
|
|
||||||
|
@property |
||||||
|
def h(self): |
||||||
|
"""Height of each frame in pixels.""" |
||||||
|
return self._frame_readers[self._first_camera_idx].h |
||||||
|
|
||||||
|
def get(self, frame_id, **kwargs): |
||||||
|
"""Get a frame for a route based on frameId. |
||||||
|
|
||||||
|
Inputs: |
||||||
|
frame_id: The frameId of the returned frame. |
||||||
|
kwargs: Forwarded to BaseFrameReader.get. "count" is not implemented. |
||||||
|
""" |
||||||
|
segment_num, segment_id = self._frame_id_lookup.get(frame_id, (None, None)) |
||||||
|
if segment_num is None or segment_num == -1 or segment_id == -1: |
||||||
|
return None |
||||||
|
else: |
||||||
|
return self.get_from_segment(segment_num, segment_id, **kwargs) |
||||||
|
|
||||||
|
def get_from_segment(self, segment_num, segment_id, **kwargs): |
||||||
|
"""Get a frame from a specific segment with a specific index in that segment (segment_id). |
||||||
|
|
||||||
|
Inputs: |
||||||
|
segment_num: The number of the segment. |
||||||
|
segment_id: The index of the return frame within that segment. |
||||||
|
kwargs: Forwarded to BaseFrameReader.get. "count" is not implemented. |
||||||
|
""" |
||||||
|
if "count" in kwargs: |
||||||
|
raise NotImplementedError("count") |
||||||
|
|
||||||
|
return self._frame_readers[segment_num].get(segment_id, **kwargs)[0] |
||||||
|
|
||||||
|
|
||||||
|
def close(self): |
||||||
|
frs = self._frame_readers |
||||||
|
self._frame_readers.clear() |
||||||
|
for fr in frs: |
||||||
|
fr.close() |
||||||
|
|
||||||
|
def __enter__(self): return self |
||||||
|
def __exit__(self, type, value, traceback): self.close() |
@ -0,0 +1,56 @@ |
|||||||
|
#!/usr/bin/env python |
||||||
|
import unittest |
||||||
|
import requests |
||||||
|
import tempfile |
||||||
|
|
||||||
|
from collections import defaultdict |
||||||
|
import numpy as np |
||||||
|
from tools.lib.framereader import FrameReader |
||||||
|
from tools.lib.logreader import LogReader |
||||||
|
|
||||||
|
class TestReaders(unittest.TestCase): |
||||||
|
def test_logreader(self): |
||||||
|
with tempfile.NamedTemporaryFile(suffix=".bz2") as fp: |
||||||
|
r = requests.get("https://github.com/commaai/comma2k19/blob/master/Example_1/b0c9d2329ad1606b%7C2018-08-02--08-34-47/40/raw_log.bz2?raw=true") |
||||||
|
fp.write(r.content) |
||||||
|
fp.flush() |
||||||
|
|
||||||
|
lr = LogReader(fp.name) |
||||||
|
hist = defaultdict(int) |
||||||
|
for l in lr: |
||||||
|
hist[l.which()] += 1 |
||||||
|
|
||||||
|
self.assertEqual(hist['carControl'], 6000) |
||||||
|
self.assertEqual(hist['logMessage'], 6857) |
||||||
|
|
||||||
|
def test_framereader(self): |
||||||
|
with tempfile.NamedTemporaryFile(suffix=".hevc") as fp: |
||||||
|
r = requests.get("https://github.com/commaai/comma2k19/blob/master/Example_1/b0c9d2329ad1606b%7C2018-08-02--08-34-47/40/video.hevc?raw=true") |
||||||
|
fp.write(r.content) |
||||||
|
fp.flush() |
||||||
|
|
||||||
|
f = FrameReader(fp.name) |
||||||
|
|
||||||
|
self.assertEqual(f.frame_count, 1200) |
||||||
|
self.assertEqual(f.w, 1164) |
||||||
|
self.assertEqual(f.h, 874) |
||||||
|
|
||||||
|
|
||||||
|
frame_first_30 = f.get(0, 30) |
||||||
|
self.assertEqual(len(frame_first_30), 30) |
||||||
|
|
||||||
|
|
||||||
|
print(frame_first_30[15]) |
||||||
|
|
||||||
|
print("frame_0") |
||||||
|
frame_0 = f.get(0, 1) |
||||||
|
frame_15 = f.get(15, 1) |
||||||
|
|
||||||
|
print(frame_15[0]) |
||||||
|
|
||||||
|
assert np.all(frame_first_30[0] == frame_0[0]) |
||||||
|
assert np.all(frame_first_30[15] == frame_15[0]) |
||||||
|
|
||||||
|
if __name__ == "__main__": |
||||||
|
unittest.main() |
||||||
|
|
@ -0,0 +1 @@ |
|||||||
|
vidindex |
@ -0,0 +1,6 @@ |
|||||||
|
CC := gcc
|
||||||
|
|
||||||
|
vidindex: bitstream.c bitstream.h vidindex.c |
||||||
|
$(eval $@_TMP := $(shell mktemp))
|
||||||
|
$(CC) -std=c99 bitstream.c vidindex.c -o $($@_TMP)
|
||||||
|
mv $($@_TMP) $@
|
@ -0,0 +1,118 @@ |
|||||||
|
#include <stdbool.h> |
||||||
|
#include <assert.h> |
||||||
|
|
||||||
|
#include "bitstream.h" |
||||||
|
|
||||||
|
static const uint32_t BS_MASKS[33] = { |
||||||
|
0, 0x1L, 0x3L, 0x7L, 0xFL, 0x1FL, |
||||||
|
0x3FL, 0x7FL, 0xFFL, 0x1FFL, 0x3FFL, 0x7FFL, |
||||||
|
0xFFFL, 0x1FFFL, 0x3FFFL, 0x7FFFL, 0xFFFFL, 0x1FFFFL, |
||||||
|
0x3FFFFL, 0x7FFFFL, 0xFFFFFL, 0x1FFFFFL, 0x3FFFFFL, 0x7FFFFFL, |
||||||
|
0xFFFFFFL, 0x1FFFFFFL, 0x3FFFFFFL, 0x7FFFFFFL, 0xFFFFFFFL, 0x1FFFFFFFL, |
||||||
|
0x3FFFFFFFL, 0x7FFFFFFFL, 0xFFFFFFFFL}; |
||||||
|
|
||||||
|
void bs_init(struct bitstream* bs, const uint8_t* buffer, size_t input_size) { |
||||||
|
bs->buffer_ptr = buffer; |
||||||
|
bs->buffer_end = buffer + input_size; |
||||||
|
bs->value = 0; |
||||||
|
bs->pos = 0; |
||||||
|
bs->shift = 8; |
||||||
|
bs->size = input_size * 8; |
||||||
|
} |
||||||
|
|
||||||
|
uint32_t bs_get(struct bitstream* bs, int n) { |
||||||
|
if (n > 32) |
||||||
|
return 0; |
||||||
|
|
||||||
|
bs->pos += n; |
||||||
|
bs->shift += n; |
||||||
|
while (bs->shift > 8) { |
||||||
|
if (bs->buffer_ptr < bs->buffer_end) { |
||||||
|
bs->value <<= 8; |
||||||
|
bs->value |= *bs->buffer_ptr++; |
||||||
|
bs->shift -= 8; |
||||||
|
} else { |
||||||
|
bs_seek(bs, bs->pos - n); |
||||||
|
return 0; |
||||||
|
// bs->value <<= 8;
|
||||||
|
// bs->shift -= 8;
|
||||||
|
} |
||||||
|
} |
||||||
|
return (bs->value >> (8 - bs->shift)) & BS_MASKS[n]; |
||||||
|
} |
||||||
|
|
||||||
|
void bs_seek(struct bitstream* bs, size_t new_pos) { |
||||||
|
bs->pos = (new_pos / 32) * 32; |
||||||
|
bs->shift = 8; |
||||||
|
bs->value = 0; |
||||||
|
bs_get(bs, new_pos % 32); |
||||||
|
} |
||||||
|
|
||||||
|
uint32_t bs_peek(struct bitstream* bs, int n) { |
||||||
|
struct bitstream bak = *bs; |
||||||
|
return bs_get(&bak, n); |
||||||
|
} |
||||||
|
|
||||||
|
size_t bs_remain(struct bitstream* bs) { |
||||||
|
return bs->size - bs->pos; |
||||||
|
} |
||||||
|
|
||||||
|
int bs_eof(struct bitstream* bs) { |
||||||
|
return bs_remain(bs) == 0; |
||||||
|
} |
||||||
|
|
||||||
|
uint32_t bs_ue(struct bitstream* bs) { |
||||||
|
static const uint8_t exp_golomb_bits[256] = { |
||||||
|
8, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, |
||||||
|
3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, |
||||||
|
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, |
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
||||||
|
}; |
||||||
|
uint32_t bits, read = 0; |
||||||
|
int bits_left; |
||||||
|
uint8_t coded; |
||||||
|
int done = 0; |
||||||
|
bits = 0; |
||||||
|
// we want to read 8 bits at a time - if we don't have 8 bits,
|
||||||
|
// read what's left, and shift. The exp_golomb_bits calc remains the
|
||||||
|
// same.
|
||||||
|
while (!done) { |
||||||
|
bits_left = bs_remain(bs); |
||||||
|
if (bits_left < 8) { |
||||||
|
read = bs_peek(bs, bits_left) << (8 - bits_left); |
||||||
|
done = 1; |
||||||
|
} else { |
||||||
|
read = bs_peek(bs, 8); |
||||||
|
if (read == 0) { |
||||||
|
bs_get(bs, 8); |
||||||
|
bits += 8; |
||||||
|
} else { |
||||||
|
done = 1; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
coded = exp_golomb_bits[read]; |
||||||
|
bs_get(bs, coded); |
||||||
|
bits += coded; |
||||||
|
|
||||||
|
// printf("ue - bits %d\n", bits);
|
||||||
|
return bs_get(bs, bits + 1) - 1; |
||||||
|
} |
||||||
|
|
||||||
|
int32_t bs_se(struct bitstream* bs) { |
||||||
|
uint32_t ret; |
||||||
|
ret = bs_ue(bs); |
||||||
|
if ((ret & 0x1) == 0) { |
||||||
|
ret >>= 1; |
||||||
|
int32_t temp = 0 - ret; |
||||||
|
return temp; |
||||||
|
} |
||||||
|
return (ret + 1) >> 1; |
||||||
|
} |
@ -0,0 +1,26 @@ |
|||||||
|
#ifndef bitstream_H |
||||||
|
#define bitstream_H |
||||||
|
|
||||||
|
|
||||||
|
#include <stddef.h> |
||||||
|
#include <stdint.h> |
||||||
|
|
||||||
|
struct bitstream { |
||||||
|
const uint8_t *buffer_ptr; |
||||||
|
const uint8_t *buffer_end; |
||||||
|
uint64_t value; |
||||||
|
uint32_t pos; |
||||||
|
uint32_t shift; |
||||||
|
size_t size; |
||||||
|
}; |
||||||
|
|
||||||
|
void bs_init(struct bitstream *bs, const uint8_t *buffer, size_t input_size); |
||||||
|
void bs_seek(struct bitstream *bs, size_t new_pos); |
||||||
|
uint32_t bs_get(struct bitstream *bs, int n); |
||||||
|
uint32_t bs_peek(struct bitstream *bs, int n); |
||||||
|
size_t bs_remain(struct bitstream *bs); |
||||||
|
int bs_eof(struct bitstream *bs); |
||||||
|
uint32_t bs_ue(struct bitstream *bs); |
||||||
|
int32_t bs_se(struct bitstream *bs); |
||||||
|
|
||||||
|
#endif |
@ -0,0 +1,307 @@ |
|||||||
|
#include <stdio.h> |
||||||
|
#include <stdlib.h> |
||||||
|
#include <stdbool.h> |
||||||
|
#include <string.h> |
||||||
|
#include <assert.h> |
||||||
|
|
||||||
|
#include <unistd.h> |
||||||
|
#include <fcntl.h> |
||||||
|
#include <sys/stat.h> |
||||||
|
#include <sys/mman.h> |
||||||
|
|
||||||
|
#include "bitstream.h" |
||||||
|
|
||||||
|
#define START_CODE 0x000001 |
||||||
|
|
||||||
|
static uint32_t read24be(const uint8_t* ptr) { |
||||||
|
return (ptr[0] << 16) | (ptr[1] << 8) | ptr[2]; |
||||||
|
} |
||||||
|
static void write32le(FILE *of, uint32_t v) { |
||||||
|
uint8_t va[4] = { |
||||||
|
v & 0xff, (v >> 8) & 0xff, (v >> 16) & 0xff, (v >> 24) & 0xff
|
||||||
|
}; |
||||||
|
fwrite(va, 1, sizeof(va), of); |
||||||
|
} |
||||||
|
|
||||||
|
// Table 7-1
|
||||||
|
enum hevc_nal_type { |
||||||
|
HEVC_NAL_TYPE_TRAIL_N = 0, |
||||||
|
HEVC_NAL_TYPE_TRAIL_R = 1, |
||||||
|
HEVC_NAL_TYPE_TSA_N = 2, |
||||||
|
HEVC_NAL_TYPE_TSA_R = 3, |
||||||
|
HEVC_NAL_TYPE_STSA_N = 4, |
||||||
|
HEVC_NAL_TYPE_STSA_R = 5, |
||||||
|
HEVC_NAL_TYPE_RADL_N = 6, |
||||||
|
HEVC_NAL_TYPE_RADL_R = 7, |
||||||
|
HEVC_NAL_TYPE_RASL_N = 8, |
||||||
|
HEVC_NAL_TYPE_RASL_R = 9, |
||||||
|
HEVC_NAL_TYPE_BLA_W_LP = 16, |
||||||
|
HEVC_NAL_TYPE_BLA_W_RADL = 17, |
||||||
|
HEVC_NAL_TYPE_BLA_N_LP = 18, |
||||||
|
HEVC_NAL_TYPE_IDR_W_RADL = 19, |
||||||
|
HEVC_NAL_TYPE_IDR_N_LP = 20, |
||||||
|
HEVC_NAL_TYPE_CRA_NUT = 21, |
||||||
|
HEVC_NAL_TYPE_RSV_IRAP_VCL23 = 23, |
||||||
|
HEVC_NAL_TYPE_VPS_NUT = 32, |
||||||
|
HEVC_NAL_TYPE_SPS_NUT = 33, |
||||||
|
HEVC_NAL_TYPE_PPS_NUT = 34, |
||||||
|
HEVC_NAL_TYPE_AUD_NUT = 35, |
||||||
|
HEVC_NAL_TYPE_EOS_NUT = 36, |
||||||
|
HEVC_NAL_TYPE_EOB_NUT = 37, |
||||||
|
HEVC_NAL_TYPE_FD_NUT = 38, |
||||||
|
HEVC_NAL_TYPE_PREFIX_SEI_NUT = 39, |
||||||
|
HEVC_NAL_TYPE_SUFFIX_SEI_NUT = 40, |
||||||
|
}; |
||||||
|
|
||||||
|
// Table 7-7
|
||||||
|
enum hevc_slice_type { |
||||||
|
HEVC_SLICE_B = 0, |
||||||
|
HEVC_SLICE_P = 1, |
||||||
|
HEVC_SLICE_I = 2, |
||||||
|
}; |
||||||
|
|
||||||
|
static void hevc_index(const uint8_t *data, size_t file_size, FILE *of_prefix, FILE *of_index) { |
||||||
|
const uint8_t* ptr = data; |
||||||
|
const uint8_t* ptr_end = data + file_size; |
||||||
|
|
||||||
|
assert(ptr[0] == 0); |
||||||
|
ptr++; |
||||||
|
assert(read24be(ptr) == START_CODE); |
||||||
|
|
||||||
|
// pps. ignore for now
|
||||||
|
uint32_t num_extra_slice_header_bits = 0; |
||||||
|
uint32_t dependent_slice_segments_enabled_flag = 0; |
||||||
|
|
||||||
|
while (ptr < ptr_end) { |
||||||
|
const uint8_t* next = ptr+1; |
||||||
|
for (; next < ptr_end-4; next++) { |
||||||
|
if (read24be(next) == START_CODE) break; |
||||||
|
} |
||||||
|
size_t nal_size = next - ptr; |
||||||
|
if (nal_size < 6) { |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
{ |
||||||
|
struct bitstream bs = {0}; |
||||||
|
bs_init(&bs, ptr, nal_size); |
||||||
|
|
||||||
|
uint32_t start_code = bs_get(&bs, 24); |
||||||
|
assert(start_code == 0x000001); |
||||||
|
|
||||||
|
// nal_unit_header
|
||||||
|
uint32_t forbidden_zero_bit = bs_get(&bs, 1); |
||||||
|
uint32_t nal_unit_type = bs_get(&bs, 6); |
||||||
|
uint32_t nuh_layer_id = bs_get(&bs, 6); |
||||||
|
uint32_t nuh_temporal_id_plus1 = bs_get(&bs, 3); |
||||||
|
|
||||||
|
// if (nal_unit_type != 1) printf("%3d -- %3d %10d %lu\n", nal_unit_type, frame_num, (uint32_t)(ptr-data), nal_size);
|
||||||
|
|
||||||
|
switch (nal_unit_type) { |
||||||
|
case HEVC_NAL_TYPE_VPS_NUT: |
||||||
|
case HEVC_NAL_TYPE_SPS_NUT: |
||||||
|
case HEVC_NAL_TYPE_PPS_NUT: |
||||||
|
fwrite(ptr, 1, nal_size, of_prefix); |
||||||
|
break; |
||||||
|
case HEVC_NAL_TYPE_TRAIL_N: |
||||||
|
case HEVC_NAL_TYPE_TRAIL_R: |
||||||
|
case HEVC_NAL_TYPE_TSA_N: |
||||||
|
case HEVC_NAL_TYPE_TSA_R: |
||||||
|
case HEVC_NAL_TYPE_STSA_N: |
||||||
|
case HEVC_NAL_TYPE_STSA_R: |
||||||
|
case HEVC_NAL_TYPE_RADL_N: |
||||||
|
case HEVC_NAL_TYPE_RADL_R: |
||||||
|
case HEVC_NAL_TYPE_RASL_N: |
||||||
|
case HEVC_NAL_TYPE_RASL_R: |
||||||
|
case HEVC_NAL_TYPE_BLA_W_LP: |
||||||
|
case HEVC_NAL_TYPE_BLA_W_RADL: |
||||||
|
case HEVC_NAL_TYPE_BLA_N_LP: |
||||||
|
case HEVC_NAL_TYPE_IDR_W_RADL: |
||||||
|
case HEVC_NAL_TYPE_IDR_N_LP: |
||||||
|
case HEVC_NAL_TYPE_CRA_NUT: { |
||||||
|
// slice_segment_header
|
||||||
|
uint32_t first_slice_segment_in_pic_flag = bs_get(&bs, 1); |
||||||
|
if (nal_unit_type >= HEVC_NAL_TYPE_BLA_W_LP && nal_unit_type <= HEVC_NAL_TYPE_RSV_IRAP_VCL23) { |
||||||
|
uint32_t no_output_of_prior_pics_flag = bs_get(&bs, 1); |
||||||
|
} |
||||||
|
uint32_t slice_pic_parameter_set_id = bs_get(&bs, 1); |
||||||
|
if (!first_slice_segment_in_pic_flag) { |
||||||
|
// ...
|
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
if (!dependent_slice_segments_enabled_flag) { |
||||||
|
for (int i=0; i<num_extra_slice_header_bits; i++) { |
||||||
|
bs_get(&bs, 1); |
||||||
|
} |
||||||
|
uint32_t slice_type = bs_ue(&bs); |
||||||
|
|
||||||
|
// write the index
|
||||||
|
write32le(of_index, slice_type); |
||||||
|
write32le(of_index, ptr - data); |
||||||
|
|
||||||
|
// ...
|
||||||
|
} |
||||||
|
|
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
//...
|
||||||
|
// emulation_prevention_three_byte
|
||||||
|
} |
||||||
|
|
||||||
|
ptr = next; |
||||||
|
} |
||||||
|
|
||||||
|
write32le(of_index, -1); |
||||||
|
write32le(of_index, file_size); |
||||||
|
} |
||||||
|
|
||||||
|
// Table 7-1
|
||||||
|
enum h264_nal_type { |
||||||
|
H264_NAL_SLICE = 1, |
||||||
|
H264_NAL_DPA = 2, |
||||||
|
H264_NAL_DPB = 3, |
||||||
|
H264_NAL_DPC = 4, |
||||||
|
H264_NAL_IDR_SLICE = 5, |
||||||
|
H264_NAL_SEI = 6, |
||||||
|
H264_NAL_SPS = 7, |
||||||
|
H264_NAL_PPS = 8, |
||||||
|
H264_NAL_AUD = 9, |
||||||
|
H264_NAL_END_SEQUENCE = 10, |
||||||
|
H264_NAL_END_STREAM = 11, |
||||||
|
H264_NAL_FILLER_DATA = 12, |
||||||
|
H264_NAL_SPS_EXT = 13, |
||||||
|
H264_NAL_AUXILIARY_SLICE = 19, |
||||||
|
}; |
||||||
|
|
||||||
|
enum h264_slice_type { |
||||||
|
H264_SLICE_P = 0, |
||||||
|
H264_SLICE_B = 1, |
||||||
|
H264_SLICE_I = 2, |
||||||
|
// ...
|
||||||
|
}; |
||||||
|
|
||||||
|
static void h264_index(const uint8_t *data, size_t file_size, FILE *of_prefix, FILE *of_index) { |
||||||
|
const uint8_t* ptr = data; |
||||||
|
const uint8_t* ptr_end = data + file_size; |
||||||
|
|
||||||
|
assert(ptr[0] == 0); |
||||||
|
ptr++; |
||||||
|
assert(read24be(ptr) == START_CODE); |
||||||
|
|
||||||
|
|
||||||
|
uint32_t sps_log2_max_frame_num_minus4; |
||||||
|
|
||||||
|
|
||||||
|
int last_frame_num = -1; |
||||||
|
|
||||||
|
while (ptr < ptr_end) { |
||||||
|
const uint8_t* next = ptr+1; |
||||||
|
for (; next < ptr_end-4; next++) { |
||||||
|
if (read24be(next) == START_CODE) break; |
||||||
|
} |
||||||
|
size_t nal_size = next - ptr; |
||||||
|
if (nal_size < 5) { |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
{ |
||||||
|
struct bitstream bs = {0}; |
||||||
|
bs_init(&bs, ptr, nal_size); |
||||||
|
|
||||||
|
uint32_t start_code = bs_get(&bs, 24); |
||||||
|
assert(start_code == 0x000001); |
||||||
|
|
||||||
|
// nal_unit_header
|
||||||
|
uint32_t forbidden_zero_bit = bs_get(&bs, 1); |
||||||
|
uint32_t nal_ref_idx = bs_get(&bs, 2); |
||||||
|
uint32_t nal_unit_type = bs_get(&bs, 5); |
||||||
|
|
||||||
|
switch (nal_unit_type) { |
||||||
|
case H264_NAL_SPS: |
||||||
|
|
||||||
|
{ |
||||||
|
uint32_t profile_idx = bs_get(&bs, 8); |
||||||
|
uint32_t constraint_sets = bs_get(&bs, 4); |
||||||
|
uint32_t reserved = bs_get(&bs, 5); |
||||||
|
uint32_t level_idc = bs_get(&bs, 5); |
||||||
|
uint32_t seq_parameter_set_id = bs_ue(&bs); |
||||||
|
sps_log2_max_frame_num_minus4 = bs_ue(&bs); |
||||||
|
} |
||||||
|
|
||||||
|
// fallthrough
|
||||||
|
case H264_NAL_PPS: |
||||||
|
fwrite(ptr, 1, nal_size, of_prefix); |
||||||
|
break; |
||||||
|
|
||||||
|
case H264_NAL_SLICE: |
||||||
|
case H264_NAL_IDR_SLICE: { |
||||||
|
// slice header
|
||||||
|
uint32_t first_mb_in_slice = bs_ue(&bs); |
||||||
|
uint32_t slice_type = bs_ue(&bs); |
||||||
|
uint32_t pic_parameter_set_id = bs_ue(&bs); |
||||||
|
|
||||||
|
uint32_t frame_num = bs_get(&bs, sps_log2_max_frame_num_minus4+4); |
||||||
|
|
||||||
|
if (first_mb_in_slice == 0) { |
||||||
|
write32le(of_index, slice_type); |
||||||
|
write32le(of_index, ptr - data); |
||||||
|
} |
||||||
|
|
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
ptr = next; |
||||||
|
} |
||||||
|
|
||||||
|
write32le(of_index, -1); |
||||||
|
write32le(of_index, file_size); |
||||||
|
} |
||||||
|
|
||||||
|
int main(int argc, char** argv) { |
||||||
|
if (argc != 5) { |
||||||
|
fprintf(stderr, "usage: %s h264|hevc file_path out_prefix out_index\n", argv[0]); |
||||||
|
exit(1); |
||||||
|
} |
||||||
|
|
||||||
|
const char* file_type = argv[1]; |
||||||
|
const char* file_path = argv[2]; |
||||||
|
|
||||||
|
int fd = open(file_path, O_RDONLY, 0); |
||||||
|
if (fd < 0) { |
||||||
|
fprintf(stderr, "error: couldn't open %s\n", file_path); |
||||||
|
exit(1); |
||||||
|
} |
||||||
|
|
||||||
|
FILE *of_prefix = fopen(argv[3], "wb"); |
||||||
|
assert(of_prefix); |
||||||
|
FILE *of_index = fopen(argv[4], "wb"); |
||||||
|
assert(of_index); |
||||||
|
|
||||||
|
off_t file_size = lseek(fd, 0, SEEK_END); |
||||||
|
lseek(fd, 0, SEEK_SET); |
||||||
|
|
||||||
|
assert(file_size > 4); |
||||||
|
|
||||||
|
const uint8_t* data = (const uint8_t*)mmap(NULL, file_size, PROT_READ, MAP_PRIVATE, fd, 0); |
||||||
|
assert(data != MAP_FAILED); |
||||||
|
|
||||||
|
if (strcmp(file_type, "hevc") == 0) { |
||||||
|
hevc_index(data, file_size, of_prefix, of_index); |
||||||
|
} else if (strcmp(file_type, "h264") == 0) { |
||||||
|
h264_index(data, file_size, of_prefix, of_index); |
||||||
|
} else { |
||||||
|
assert(false); |
||||||
|
} |
||||||
|
|
||||||
|
munmap((void*)data, file_size); |
||||||
|
close(fd); |
||||||
|
|
||||||
|
return 0; |
||||||
|
} |
@ -0,0 +1,30 @@ |
|||||||
|
import numpy as np |
||||||
|
import cv2 |
||||||
|
|
||||||
|
def rot_matrix(roll, pitch, yaw): |
||||||
|
cr, sr = np.cos(roll), np.sin(roll) |
||||||
|
cp, sp = np.cos(pitch), np.sin(pitch) |
||||||
|
cy, sy = np.cos(yaw), np.sin(yaw) |
||||||
|
rr = np.array([[1,0,0],[0, cr,-sr],[0, sr, cr]]) |
||||||
|
rp = np.array([[cp,0,sp],[0, 1,0],[-sp, 0, cp]]) |
||||||
|
ry = np.array([[cy,-sy,0],[sy, cy,0],[0, 0, 1]]) |
||||||
|
return ry.dot(rp.dot(rr)) |
||||||
|
|
||||||
|
def draw_pose(img, pose, loc, W=160, H=320, xyoffset=(0,0), faceprob=0): |
||||||
|
rcmat = np.zeros((3,4)) |
||||||
|
rcmat[:,:3] = rot_matrix(*pose[0:3]) * 0.5 |
||||||
|
rcmat[0,3] = (loc[0]+0.5) * W |
||||||
|
rcmat[1,3] = (loc[1]+0.5) * H |
||||||
|
rcmat[2,3] = 1.0 |
||||||
|
# draw nose |
||||||
|
p1 = np.dot(rcmat, [0,0,0,1])[0:2] |
||||||
|
p2 = np.dot(rcmat, [0,0,100,1])[0:2] |
||||||
|
tr = tuple([int(round(x + xyoffset[i])) for i,x in enumerate(p1)]) |
||||||
|
pr = tuple([int(round(x + xyoffset[i])) for i,x in enumerate(p2)]) |
||||||
|
if faceprob > 0.4: |
||||||
|
color = (255,255,0) |
||||||
|
cv2.line(img, tr, pr, color=(255,255,0), thickness=3) |
||||||
|
else: |
||||||
|
color = (64,64,64) |
||||||
|
cv2.circle(img, tr, 7, color=color) |
||||||
|
|
@ -0,0 +1,79 @@ |
|||||||
|
#!/usr/bin/env python3 |
||||||
|
import os |
||||||
|
import argparse |
||||||
|
import pygame |
||||||
|
import numpy as np |
||||||
|
import cv2 |
||||||
|
|
||||||
|
from cereal import log |
||||||
|
import cereal.messaging as messaging |
||||||
|
|
||||||
|
from helpers import draw_pose |
||||||
|
|
||||||
|
if __name__ == "__main__": |
||||||
|
|
||||||
|
os.environ["ZMQ"] = "1" |
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description='Sniff a communcation socket') |
||||||
|
parser.add_argument('--addr', default='192.168.5.11') |
||||||
|
args = parser.parse_args() |
||||||
|
|
||||||
|
messaging.context = messaging.Context() |
||||||
|
|
||||||
|
poller = messaging.Poller() |
||||||
|
|
||||||
|
m = 'driverMonitoring' |
||||||
|
sock = messaging.sub_sock(m, poller, addr=args.addr) |
||||||
|
|
||||||
|
pygame.init() |
||||||
|
pygame.display.set_caption('livedm') |
||||||
|
screen = pygame.display.set_mode((320,640), pygame.DOUBLEBUF) |
||||||
|
camera_surface = pygame.surface.Surface((160,320), 0, 24).convert() |
||||||
|
|
||||||
|
while 1: |
||||||
|
polld = poller.poll(1000) |
||||||
|
for sock in polld: |
||||||
|
msg = sock.receive() |
||||||
|
evt = log.Event.from_bytes(msg) |
||||||
|
|
||||||
|
faceProb = np.array(evt.driverMonitoring.faceProb) |
||||||
|
faceOrientation = np.array(evt.driverMonitoring.faceOrientation) |
||||||
|
facePosition = np.array(evt.driverMonitoring.facePosition) |
||||||
|
|
||||||
|
print(faceProb) |
||||||
|
# print(faceOrientation) |
||||||
|
# print(facePosition) |
||||||
|
faceOrientation[1] *= -1 |
||||||
|
facePosition[0] *= -1 |
||||||
|
|
||||||
|
img = np.zeros((320,160,3)) |
||||||
|
if faceProb > 0.4: |
||||||
|
cv2.putText(img, 'you', (int(facePosition[0]*160+40), int(facePosition[1]*320+110)), cv2.FONT_ITALIC, 0.5, (255,255,0)) |
||||||
|
cv2.rectangle(img, (int(facePosition[0]*160+40), int(facePosition[1]*320+120)),\ |
||||||
|
(int(facePosition[0]*160+120), int(facePosition[1]*320+200)), (255,255,0), 1) |
||||||
|
|
||||||
|
not_blink = evt.driverMonitoring.leftBlinkProb + evt.driverMonitoring.rightBlinkProb < 1 |
||||||
|
|
||||||
|
if evt.driverMonitoring.leftEyeProb > 0.6: |
||||||
|
cv2.line(img, (int(facePosition[0]*160+95), int(facePosition[1]*320+140)),\ |
||||||
|
(int(facePosition[0]*160+105), int(facePosition[1]*320+140)), (255,255,0), 2) |
||||||
|
if not_blink: |
||||||
|
cv2.line(img, (int(facePosition[0]*160+99), int(facePosition[1]*320+143)),\ |
||||||
|
(int(facePosition[0]*160+101), int(facePosition[1]*320+143)), (255,255,0), 2) |
||||||
|
|
||||||
|
if evt.driverMonitoring.rightEyeProb > 0.6: |
||||||
|
cv2.line(img, (int(facePosition[0]*160+55), int(facePosition[1]*320+140)),\ |
||||||
|
(int(facePosition[0]*160+65), int(facePosition[1]*320+140)), (255,255,0), 2) |
||||||
|
if not_blink: |
||||||
|
cv2.line(img, (int(facePosition[0]*160+59), int(facePosition[1]*320+143)),\ |
||||||
|
(int(facePosition[0]*160+61), int(facePosition[1]*320+143)), (255,255,0), 2) |
||||||
|
|
||||||
|
else: |
||||||
|
cv2.putText(img, 'you not found', (int(facePosition[0]*160+40), int(facePosition[1]*320+110)), cv2.FONT_ITALIC, 0.5, (64,64,64)) |
||||||
|
draw_pose(img, faceOrientation, facePosition, |
||||||
|
W = 160, H = 320, xyoffset = (0, 0), faceprob=faceProb) |
||||||
|
|
||||||
|
pygame.surfarray.blit_array(camera_surface, img.swapaxes(0,1)) |
||||||
|
camera_surface_2x = pygame.transform.scale2x(camera_surface) |
||||||
|
screen.blit(camera_surface_2x, (0, 0)) |
||||||
|
pygame.display.flip() |
@ -0,0 +1,59 @@ |
|||||||
|
#!/usr/bin/env python |
||||||
|
import argparse |
||||||
|
import os |
||||||
|
import sys |
||||||
|
from common.basedir import BASEDIR |
||||||
|
from tools.lib.logreader import MultiLogIterator |
||||||
|
from tools.lib.route import Route |
||||||
|
|
||||||
|
os.environ['BASEDIR'] = BASEDIR |
||||||
|
|
||||||
|
|
||||||
|
def get_arg_parser(): |
||||||
|
parser = argparse.ArgumentParser( |
||||||
|
description="Unlogging and save to file", |
||||||
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter) |
||||||
|
|
||||||
|
parser.add_argument("data_dir", nargs='?', |
||||||
|
help="Path to directory in which log and camera files are located.") |
||||||
|
parser.add_argument("route_name", type=(lambda x: x.replace("#", "|")), nargs="?", |
||||||
|
help="The route whose messages will be published.") |
||||||
|
parser.add_argument("--out_path", nargs='?', default='/data/ubloxRaw.stream', |
||||||
|
help="Output pickle file path") |
||||||
|
return parser |
||||||
|
|
||||||
|
|
||||||
|
def main(argv): |
||||||
|
args = get_arg_parser().parse_args(sys.argv[1:]) |
||||||
|
if not args.data_dir: |
||||||
|
print('Data directory invalid.') |
||||||
|
return |
||||||
|
|
||||||
|
if not args.route_name: |
||||||
|
# Extract route name from path |
||||||
|
args.route_name = os.path.basename(args.data_dir) |
||||||
|
args.data_dir = os.path.dirname(args.data_dir) |
||||||
|
|
||||||
|
route = Route(args.route_name, args.data_dir) |
||||||
|
lr = MultiLogIterator(route.log_paths(), wraparound=False) |
||||||
|
|
||||||
|
with open(args.out_path, 'wb') as f: |
||||||
|
try: |
||||||
|
done = False |
||||||
|
i = 0 |
||||||
|
while not done: |
||||||
|
msg = next(lr) |
||||||
|
if not msg: |
||||||
|
break |
||||||
|
smsg = msg.as_builder() |
||||||
|
typ = smsg.which() |
||||||
|
if typ == 'ubloxRaw': |
||||||
|
f.write(smsg.to_bytes()) |
||||||
|
i += 1 |
||||||
|
except StopIteration: |
||||||
|
print('All done') |
||||||
|
print('Writed {} msgs'.format(i)) |
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__": |
||||||
|
sys.exit(main(sys.argv[1:])) |
@ -0,0 +1,8 @@ |
|||||||
|
Makefile |
||||||
|
.*.swp |
||||||
|
*.o |
||||||
|
nui |
||||||
|
moc_* |
||||||
|
.qmake.stash |
||||||
|
nui.app/* |
||||||
|
|
@ -0,0 +1,138 @@ |
|||||||
|
#include "FileReader.hpp" |
||||||
|
#include "FrameReader.hpp" |
||||||
|
|
||||||
|
#include <QtNetwork> |
||||||
|
|
||||||
|
FileReader::FileReader(const QString& file_) : file(file_) { |
||||||
|
} |
||||||
|
|
||||||
|
void FileReader::process() { |
||||||
|
timer.start(); |
||||||
|
// TODO: Support reading files from the API
|
||||||
|
startRequest(QUrl("http://data.comma.life/"+file)); |
||||||
|
} |
||||||
|
|
||||||
|
void FileReader::startRequest(const QUrl &url) { |
||||||
|
qnam = new QNetworkAccessManager; |
||||||
|
reply = qnam->get(QNetworkRequest(url)); |
||||||
|
connect(reply, &QNetworkReply::finished, this, &FileReader::httpFinished); |
||||||
|
connect(reply, &QIODevice::readyRead, this, &FileReader::readyRead); |
||||||
|
qDebug() << "requesting" << url; |
||||||
|
} |
||||||
|
|
||||||
|
void FileReader::httpFinished() { |
||||||
|
if (reply->error()) { |
||||||
|
qWarning() << reply->errorString(); |
||||||
|
} |
||||||
|
|
||||||
|
const QVariant redirectionTarget = reply->attribute(QNetworkRequest::RedirectionTargetAttribute); |
||||||
|
if (!redirectionTarget.isNull()) { |
||||||
|
const QUrl redirectedUrl = redirectionTarget.toUrl(); |
||||||
|
//qDebug() << "redirected to" << redirectedUrl;
|
||||||
|
startRequest(redirectedUrl); |
||||||
|
} else { |
||||||
|
qDebug() << "done in" << timer.elapsed() << "ms"; |
||||||
|
done(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void FileReader::readyRead() { |
||||||
|
QByteArray dat = reply->readAll(); |
||||||
|
printf("got http ready read: %d\n", dat.size()); |
||||||
|
} |
||||||
|
|
||||||
|
FileReader::~FileReader() { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
LogReader::LogReader(const QString& file, Events *events_, QReadWriteLock* events_lock_, QMap<int, QPair<int, int> > *eidx_) : |
||||||
|
FileReader(file), events(events_), events_lock(events_lock_), eidx(eidx_) { |
||||||
|
bStream.next_in = NULL; |
||||||
|
bStream.avail_in = 0; |
||||||
|
bStream.bzalloc = NULL; |
||||||
|
bStream.bzfree = NULL; |
||||||
|
bStream.opaque = NULL; |
||||||
|
|
||||||
|
int ret = BZ2_bzDecompressInit(&bStream, 0, 0); |
||||||
|
if (ret != BZ_OK) qWarning() << "bz2 init failed"; |
||||||
|
|
||||||
|
// start with 64MB buffer
|
||||||
|
raw.resize(1024*1024*64); |
||||||
|
|
||||||
|
// auto increment?
|
||||||
|
bStream.next_out = raw.data(); |
||||||
|
bStream.avail_out = raw.size(); |
||||||
|
|
||||||
|
// parsed no events yet
|
||||||
|
event_offset = 0; |
||||||
|
|
||||||
|
parser = new std::thread([&]() { |
||||||
|
while (1) { |
||||||
|
mergeEvents(cdled.get()); |
||||||
|
} |
||||||
|
});
|
||||||
|
} |
||||||
|
|
||||||
|
void LogReader::mergeEvents(int dled) { |
||||||
|
auto amsg = kj::arrayPtr((const capnp::word*)(raw.data() + event_offset), (dled-event_offset)/sizeof(capnp::word)); |
||||||
|
Events events_local; |
||||||
|
QMap<int, QPair<int, int> > eidx_local; |
||||||
|
|
||||||
|
while (amsg.size() > 0) { |
||||||
|
try { |
||||||
|
capnp::FlatArrayMessageReader cmsg = capnp::FlatArrayMessageReader(amsg); |
||||||
|
|
||||||
|
// this needed? it is
|
||||||
|
capnp::FlatArrayMessageReader *tmsg = |
||||||
|
new capnp::FlatArrayMessageReader(kj::arrayPtr(amsg.begin(), cmsg.getEnd())); |
||||||
|
|
||||||
|
amsg = kj::arrayPtr(cmsg.getEnd(), amsg.end()); |
||||||
|
|
||||||
|
cereal::Event::Reader event = tmsg->getRoot<cereal::Event>(); |
||||||
|
events_local.insert(event.getLogMonoTime(), event); |
||||||
|
|
||||||
|
// hack
|
||||||
|
// TODO: rewrite with callback
|
||||||
|
if (event.which() == cereal::Event::ENCODE_IDX) { |
||||||
|
auto ee = event.getEncodeIdx(); |
||||||
|
eidx_local.insert(ee.getFrameId(), qMakePair(ee.getSegmentNum(), ee.getSegmentId())); |
||||||
|
} |
||||||
|
|
||||||
|
// increment
|
||||||
|
event_offset = (char*)cmsg.getEnd() - raw.data(); |
||||||
|
} catch (const kj::Exception& e) { |
||||||
|
// partial messages trigger this
|
||||||
|
//qDebug() << e.getDescription().cStr();
|
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// merge in events
|
||||||
|
// TODO: add lock
|
||||||
|
events_lock->lockForWrite(); |
||||||
|
*events += events_local; |
||||||
|
eidx->unite(eidx_local); |
||||||
|
events_lock->unlock(); |
||||||
|
|
||||||
|
printf("parsed %d into %d events with offset %d\n", dled, events->size(), event_offset); |
||||||
|
} |
||||||
|
|
||||||
|
void LogReader::readyRead() { |
||||||
|
QByteArray dat = reply->readAll(); |
||||||
|
|
||||||
|
bStream.next_in = dat.data(); |
||||||
|
bStream.avail_in = dat.size(); |
||||||
|
|
||||||
|
while (bStream.avail_in > 0) { |
||||||
|
int ret = BZ2_bzDecompress(&bStream); |
||||||
|
if (ret != BZ_OK && ret != BZ_STREAM_END) { |
||||||
|
qWarning() << "bz2 decompress failed"; |
||||||
|
break; |
||||||
|
} |
||||||
|
qDebug() << "got" << dat.size() << "with" << bStream.avail_out << "size" << raw.size(); |
||||||
|
} |
||||||
|
|
||||||
|
int dled = raw.size() - bStream.avail_out; |
||||||
|
cdled.put(dled); |
||||||
|
} |
||||||
|
|
@ -0,0 +1,68 @@ |
|||||||
|
#ifndef FILEREADER_HPP |
||||||
|
#define FILEREADER_HPP |
||||||
|
|
||||||
|
#include <QString> |
||||||
|
#include <QNetworkAccessManager> |
||||||
|
#include <QWidget> |
||||||
|
#include <QVector> |
||||||
|
#include <QMultiMap> |
||||||
|
#include <QElapsedTimer> |
||||||
|
#include <QReadWriteLock> |
||||||
|
|
||||||
|
#include <bzlib.h> |
||||||
|
|
||||||
|
#include <kj/io.h> |
||||||
|
#include <capnp/serialize.h> |
||||||
|
|
||||||
|
#include "cereal/gen/cpp/log.capnp.h" |
||||||
|
|
||||||
|
#include <thread> |
||||||
|
#include "channel.hpp" |
||||||
|
|
||||||
|
class FileReader : public QObject { |
||||||
|
Q_OBJECT |
||||||
|
public: |
||||||
|
FileReader(const QString& file_); |
||||||
|
void startRequest(const QUrl &url); |
||||||
|
~FileReader(); |
||||||
|
virtual void readyRead(); |
||||||
|
void httpFinished(); |
||||||
|
virtual void done() {}; |
||||||
|
public slots: |
||||||
|
void process(); |
||||||
|
protected: |
||||||
|
QNetworkReply *reply; |
||||||
|
private: |
||||||
|
QNetworkAccessManager *qnam; |
||||||
|
QElapsedTimer timer; |
||||||
|
QString file; |
||||||
|
}; |
||||||
|
|
||||||
|
typedef QMultiMap<uint64_t, cereal::Event::Reader> Events; |
||||||
|
|
||||||
|
class LogReader : public FileReader { |
||||||
|
Q_OBJECT |
||||||
|
public: |
||||||
|
LogReader(const QString& file, Events *, QReadWriteLock* events_lock_, QMap<int, QPair<int, int> > *eidx_); |
||||||
|
void readyRead(); |
||||||
|
void done() { is_done = true; }; |
||||||
|
bool is_done = false; |
||||||
|
private: |
||||||
|
bz_stream bStream; |
||||||
|
|
||||||
|
// backing store
|
||||||
|
QByteArray raw; |
||||||
|
|
||||||
|
std::thread *parser; |
||||||
|
int event_offset; |
||||||
|
channel<int> cdled; |
||||||
|
|
||||||
|
// global
|
||||||
|
void mergeEvents(int dled); |
||||||
|
Events *events; |
||||||
|
QReadWriteLock* events_lock; |
||||||
|
QMap<int, QPair<int, int> > *eidx; |
||||||
|
}; |
||||||
|
|
||||||
|
#endif |
||||||
|
|
@ -0,0 +1,9 @@ |
|||||||
|
== Ubuntu == |
||||||
|
|
||||||
|
sudo apt-get install capnproto libyaml-cpp-dev qt5-default |
||||||
|
|
||||||
|
== Mac == |
||||||
|
|
||||||
|
brew install qt5 ffmpeg capnp yaml-cpp zmq |
||||||
|
brew link qt5 --force |
||||||
|
|
@ -0,0 +1,182 @@ |
|||||||
|
#include <string> |
||||||
|
#include <vector> |
||||||
|
#include <yaml-cpp/yaml.h> |
||||||
|
#include <capnp/dynamic.h> |
||||||
|
#include <capnp/schema.h> |
||||||
|
|
||||||
|
// include the dynamic struct
|
||||||
|
#include "cereal/gen/cpp/car.capnp.c++" |
||||||
|
#include "cereal/gen/cpp/log.capnp.c++" |
||||||
|
|
||||||
|
#include "Unlogger.hpp" |
||||||
|
|
||||||
|
#include <stdint.h> |
||||||
|
#include <time.h> |
||||||
|
|
||||||
|
static inline uint64_t nanos_since_boot() { |
||||||
|
struct timespec t; |
||||||
|
clock_gettime(CLOCK_BOOTTIME, &t); |
||||||
|
return t.tv_sec * 1000000000ULL + t.tv_nsec; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
Unlogger::Unlogger(Events *events_, QReadWriteLock* events_lock_, QMap<int, FrameReader*> *frs_, int seek)
|
||||||
|
: events(events_), events_lock(events_lock_), frs(frs_) { |
||||||
|
ctx = Context::create(); |
||||||
|
YAML::Node service_list = YAML::LoadFile("../../cereal/service_list.yaml"); |
||||||
|
|
||||||
|
seek_request = seek*1e9; |
||||||
|
|
||||||
|
QStringList block = QString(getenv("BLOCK")).split(","); |
||||||
|
qDebug() << "blocklist" << block; |
||||||
|
|
||||||
|
QStringList allow = QString(getenv("ALLOW")).split(","); |
||||||
|
qDebug() << "allowlist" << allow; |
||||||
|
|
||||||
|
for (const auto& it : service_list) { |
||||||
|
auto name = it.first.as<std::string>(); |
||||||
|
|
||||||
|
if (allow[0].size() > 0 && !allow.contains(name.c_str())) { |
||||||
|
qDebug() << "not allowing" << name.c_str(); |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
if (block.contains(name.c_str())) { |
||||||
|
qDebug() << "blocking" << name.c_str(); |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
PubSocket *sock = PubSocket::create(ctx, name); |
||||||
|
if (sock == NULL) { |
||||||
|
qDebug() << "FAILED" << name.c_str(); |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
qDebug() << name.c_str(); |
||||||
|
|
||||||
|
for (auto field: capnp::Schema::from<cereal::Event>().getFields()) { |
||||||
|
std::string tname = field.getProto().getName(); |
||||||
|
|
||||||
|
if (tname == name) { |
||||||
|
// TODO: I couldn't figure out how to get the which, only the index, hence this hack
|
||||||
|
int type = field.getIndex(); |
||||||
|
if (type > 67) type--; // valid
|
||||||
|
type--; // logMonoTime
|
||||||
|
|
||||||
|
//qDebug() << "here" << tname.c_str() << type << cereal::Event::CONTROLS_STATE;
|
||||||
|
socks.insert(type, sock); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void Unlogger::process() { |
||||||
|
qDebug() << "hello from unlogger thread"; |
||||||
|
while (events->size() == 0) { |
||||||
|
qDebug() << "waiting for events"; |
||||||
|
QThread::sleep(1); |
||||||
|
} |
||||||
|
qDebug() << "got events"; |
||||||
|
|
||||||
|
// TODO: hack
|
||||||
|
if (seek_request != 0) { |
||||||
|
seek_request += events->begin().key(); |
||||||
|
while (events->lowerBound(seek_request) == events->end()) { |
||||||
|
qDebug() << "waiting for desired time"; |
||||||
|
QThread::sleep(1); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
QElapsedTimer timer; |
||||||
|
timer.start(); |
||||||
|
|
||||||
|
uint64_t last_elapsed = 0; |
||||||
|
|
||||||
|
// loops
|
||||||
|
while (1) { |
||||||
|
uint64_t t0 = (events->begin()+1).key(); |
||||||
|
uint64_t t0r = timer.nsecsElapsed(); |
||||||
|
qDebug() << "unlogging at" << t0; |
||||||
|
|
||||||
|
auto eit = events->lowerBound(t0); |
||||||
|
while (eit != events->end()) { |
||||||
|
while (paused) { |
||||||
|
QThread::usleep(1000); |
||||||
|
t0 = eit->getLogMonoTime(); |
||||||
|
t0r = timer.nsecsElapsed(); |
||||||
|
} |
||||||
|
|
||||||
|
if (seek_request != 0) { |
||||||
|
t0 = seek_request; |
||||||
|
qDebug() << "seeking to" << t0; |
||||||
|
t0r = timer.nsecsElapsed(); |
||||||
|
eit = events->lowerBound(t0); |
||||||
|
seek_request = 0; |
||||||
|
if (eit == events->end()) { |
||||||
|
qWarning() << "seek off end"; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (abs(((long long)tc-(long long)last_elapsed)) > 50e6) { |
||||||
|
//qDebug() << "elapsed";
|
||||||
|
emit elapsed(); |
||||||
|
last_elapsed = tc; |
||||||
|
} |
||||||
|
|
||||||
|
auto e = *eit; |
||||||
|
auto type = e.which(); |
||||||
|
uint64_t tm = e.getLogMonoTime(); |
||||||
|
auto it = socks.find(type); |
||||||
|
tc = tm; |
||||||
|
if (it != socks.end()) { |
||||||
|
long etime = tm-t0; |
||||||
|
long rtime = timer.nsecsElapsed() - t0r; |
||||||
|
long us_behind = ((etime-rtime)*1e-3)+0.5; |
||||||
|
if (us_behind > 0) { |
||||||
|
if (us_behind > 1e6) { |
||||||
|
qWarning() << "OVER ONE SECOND BEHIND, HACKING" << us_behind; |
||||||
|
us_behind = 0; |
||||||
|
t0 = tm; |
||||||
|
t0r = timer.nsecsElapsed(); |
||||||
|
} |
||||||
|
QThread::usleep(us_behind); |
||||||
|
//qDebug() << "sleeping" << us_behind << etime << timer.nsecsElapsed();
|
||||||
|
} |
||||||
|
|
||||||
|
capnp::MallocMessageBuilder msg; |
||||||
|
msg.setRoot(e); |
||||||
|
|
||||||
|
auto ee = msg.getRoot<cereal::Event>(); |
||||||
|
ee.setLogMonoTime(nanos_since_boot()); |
||||||
|
|
||||||
|
if (e.which() == cereal::Event::FRAME) { |
||||||
|
auto fr = msg.getRoot<cereal::Event>().getFrame(); |
||||||
|
|
||||||
|
// TODO: better way?
|
||||||
|
auto it = eidx.find(fr.getFrameId()); |
||||||
|
if (it != eidx.end()) { |
||||||
|
auto pp = *it; |
||||||
|
//qDebug() << fr.getFrameId() << pp;
|
||||||
|
|
||||||
|
if (frs->find(pp.first) != frs->end()) { |
||||||
|
auto frm = (*frs)[pp.first]; |
||||||
|
auto data = frm->get(pp.second); |
||||||
|
if (data != NULL) { |
||||||
|
fr.setImage(kj::arrayPtr(data, frm->getRGBSize())); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
auto words = capnp::messageToFlatArray(msg); |
||||||
|
auto bytes = words.asBytes(); |
||||||
|
|
||||||
|
// TODO: Can PubSocket take a const char?
|
||||||
|
(*it)->send((char*)bytes.begin(), bytes.size()); |
||||||
|
} |
||||||
|
++eit; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
@ -0,0 +1,36 @@ |
|||||||
|
#ifndef UNLOGGER_HPP |
||||||
|
#define UNLOGGER_HPP |
||||||
|
|
||||||
|
#include <QThread> |
||||||
|
#include <QReadWriteLock> |
||||||
|
#include "messaging.hpp" |
||||||
|
#include "FileReader.hpp" |
||||||
|
#include "FrameReader.hpp" |
||||||
|
|
||||||
|
class Unlogger : public QObject { |
||||||
|
Q_OBJECT |
||||||
|
public: |
||||||
|
Unlogger(Events *events_, QReadWriteLock* events_lock_, QMap<int, FrameReader*> *frs_, int seek); |
||||||
|
uint64_t getCurrentTime() { return tc; } |
||||||
|
void setSeekRequest(uint64_t seek_request_) { seek_request = seek_request_; } |
||||||
|
void setPause(bool pause) { paused = pause; } |
||||||
|
void togglePause() { paused = !paused; } |
||||||
|
QMap<int, QPair<int, int> > eidx; |
||||||
|
public slots: |
||||||
|
void process(); |
||||||
|
signals: |
||||||
|
void elapsed(); |
||||||
|
void finished(); |
||||||
|
private: |
||||||
|
Events *events; |
||||||
|
QReadWriteLock *events_lock; |
||||||
|
QMap<int, FrameReader*> *frs; |
||||||
|
QMap<int, PubSocket*> socks; |
||||||
|
Context *ctx; |
||||||
|
uint64_t tc = 0; |
||||||
|
uint64_t seek_request = 0; |
||||||
|
bool paused = false; |
||||||
|
}; |
||||||
|
|
||||||
|
#endif |
||||||
|
|
@ -0,0 +1,4 @@ |
|||||||
|
#!/bin/bash |
||||||
|
qmake |
||||||
|
make -j |
||||||
|
|
@ -0,0 +1,216 @@ |
|||||||
|
#include <QApplication> |
||||||
|
#include <QWidget> |
||||||
|
#include <QString> |
||||||
|
#include <QTimer> |
||||||
|
#include <QPushButton> |
||||||
|
#include <QGraphicsScene> |
||||||
|
#include <QPainter> |
||||||
|
#include <QThread> |
||||||
|
#include <QMouseEvent> |
||||||
|
#include <QReadWriteLock> |
||||||
|
#include <QLineEdit> |
||||||
|
|
||||||
|
#include "FileReader.hpp" |
||||||
|
#include "Unlogger.hpp" |
||||||
|
#include "FrameReader.hpp" |
||||||
|
|
||||||
|
class Window : public QWidget { |
||||||
|
public: |
||||||
|
Window(QString route_, int seek); |
||||||
|
bool addSegment(int i); |
||||||
|
protected: |
||||||
|
void keyPressEvent(QKeyEvent *event) override; |
||||||
|
void mousePressEvent(QMouseEvent *event) override; |
||||||
|
void paintEvent(QPaintEvent *event) override; |
||||||
|
uint64_t ct; |
||||||
|
Unlogger *unlogger; |
||||||
|
private: |
||||||
|
int timeToPixel(uint64_t ns); |
||||||
|
uint64_t pixelToTime(int px); |
||||||
|
QString route; |
||||||
|
|
||||||
|
QReadWriteLock events_lock; |
||||||
|
Events events; |
||||||
|
int last_event_size = 0; |
||||||
|
|
||||||
|
QMap<int, LogReader*> lrs; |
||||||
|
QMap<int, FrameReader*> frs; |
||||||
|
|
||||||
|
// cache the bar
|
||||||
|
QPixmap *px = NULL; |
||||||
|
int seg_add = 0; |
||||||
|
|
||||||
|
QLineEdit *timeLE; |
||||||
|
}; |
||||||
|
|
||||||
|
Window::Window(QString route_, int seek) : route(route_) { |
||||||
|
timeLE = new QLineEdit(this); |
||||||
|
timeLE->setPlaceholderText("Placeholder Text"); |
||||||
|
timeLE->move(50, 650); |
||||||
|
|
||||||
|
QThread* thread = new QThread; |
||||||
|
unlogger = new Unlogger(&events, &events_lock, &frs, seek); |
||||||
|
unlogger->moveToThread(thread); |
||||||
|
connect(thread, SIGNAL (started()), unlogger, SLOT (process())); |
||||||
|
connect(unlogger, SIGNAL (elapsed()), this, SLOT (update())); |
||||||
|
thread->start(); |
||||||
|
|
||||||
|
this->setFocusPolicy(Qt::StrongFocus); |
||||||
|
|
||||||
|
// add the first segment
|
||||||
|
addSegment(seek/60); |
||||||
|
} |
||||||
|
|
||||||
|
bool Window::addSegment(int i) { |
||||||
|
if (lrs.find(i) == lrs.end()) { |
||||||
|
QString fn = QString("%1/%2/rlog.bz2").arg(route).arg(i); |
||||||
|
|
||||||
|
QThread* thread = new QThread; |
||||||
|
lrs.insert(i, new LogReader(fn, &events, &events_lock, &unlogger->eidx)); |
||||||
|
lrs[i]->moveToThread(thread); |
||||||
|
connect(thread, SIGNAL (started()), lrs[i], SLOT (process())); |
||||||
|
thread->start(); |
||||||
|
//connect(lrs[i], SIGNAL (finished()), this, SLOT (update()));
|
||||||
|
|
||||||
|
QString frn = QString("%1/%2/fcamera.hevc").arg(route).arg(i); |
||||||
|
frs.insert(i, new FrameReader(qPrintable(frn))); |
||||||
|
return true; |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
#define PIXELS_PER_SEC 0.5 |
||||||
|
|
||||||
|
int Window::timeToPixel(uint64_t ns) { |
||||||
|
// TODO: make this dynamic
|
||||||
|
return int(ns*1e-9*PIXELS_PER_SEC+0.5); |
||||||
|
} |
||||||
|
|
||||||
|
uint64_t Window::pixelToTime(int px) { |
||||||
|
// TODO: make this dynamic
|
||||||
|
//printf("%d\n", px);
|
||||||
|
return ((px+0.5)/PIXELS_PER_SEC) * 1e9; |
||||||
|
} |
||||||
|
|
||||||
|
void Window::keyPressEvent(QKeyEvent *event) { |
||||||
|
printf("keypress: %x\n", event->key()); |
||||||
|
if (event->key() == Qt::Key_Space) unlogger->togglePause(); |
||||||
|
} |
||||||
|
|
||||||
|
void Window::mousePressEvent(QMouseEvent *event) { |
||||||
|
//printf("mouse event\n");
|
||||||
|
if (event->button() == Qt::LeftButton) { |
||||||
|
uint64_t t0 = events.begin().key(); |
||||||
|
uint64_t tt = pixelToTime(event->x()); |
||||||
|
int seg = int((tt*1e-9)/60); |
||||||
|
printf("segment %d\n", seg); |
||||||
|
addSegment(seg); |
||||||
|
|
||||||
|
//printf("seek to %lu\n", t0+tt);
|
||||||
|
unlogger->setSeekRequest(t0+tt); |
||||||
|
} |
||||||
|
this->update(); |
||||||
|
} |
||||||
|
|
||||||
|
void Window::paintEvent(QPaintEvent *event) { |
||||||
|
if (events.size() == 0) return; |
||||||
|
|
||||||
|
QElapsedTimer timer; |
||||||
|
timer.start(); |
||||||
|
|
||||||
|
uint64_t t0 = events.begin().key(); |
||||||
|
uint64_t t1 = (events.end()-1).key(); |
||||||
|
|
||||||
|
//p.drawRect(0, 0, 600, 100);
|
||||||
|
|
||||||
|
// TODO: we really don't have to redraw this every time, only on updates to events
|
||||||
|
int this_event_size = events.size(); |
||||||
|
if (last_event_size != this_event_size) { |
||||||
|
if (px != NULL) delete px; |
||||||
|
px = new QPixmap(1920, 600); |
||||||
|
px->fill(QColor(0xd8, 0xd8, 0xd8)); |
||||||
|
|
||||||
|
QPainter tt(px); |
||||||
|
tt.setBrush(Qt::cyan); |
||||||
|
|
||||||
|
int lt = -1; |
||||||
|
int lvv = 0; |
||||||
|
for (auto e : events) { |
||||||
|
auto type = e.which(); |
||||||
|
//printf("%lld %d\n", e.getLogMonoTime()-t0, type);
|
||||||
|
if (type == cereal::Event::CONTROLS_STATE) { |
||||||
|
auto controlsState = e.getControlsState(); |
||||||
|
uint64_t t = (e.getLogMonoTime()-t0); |
||||||
|
float vEgo = controlsState.getVEgo(); |
||||||
|
int enabled = controlsState.getState() == cereal::ControlsState::OpenpilotState::ENABLED; |
||||||
|
int rt = timeToPixel(t); // 250 ms per pixel
|
||||||
|
if (rt != lt) { |
||||||
|
int vv = vEgo*8.0; |
||||||
|
if (lt != -1) { |
||||||
|
tt.setPen(Qt::red); |
||||||
|
tt.drawLine(lt, 300-lvv, rt, 300-vv); |
||||||
|
|
||||||
|
if (enabled) { |
||||||
|
tt.setPen(Qt::green);
|
||||||
|
} else { |
||||||
|
tt.setPen(Qt::blue);
|
||||||
|
} |
||||||
|
|
||||||
|
tt.drawLine(rt, 300, rt, 600); |
||||||
|
} |
||||||
|
lt = rt; |
||||||
|
lvv = vv; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
tt.end(); |
||||||
|
last_event_size = this_event_size; |
||||||
|
if (lrs.find(seg_add) != lrs.end() && lrs[seg_add]->is_done) { |
||||||
|
while (!addSegment(++seg_add)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
QPainter p(this); |
||||||
|
if (px != NULL) p.drawPixmap(0, 0, 1920, 600, *px); |
||||||
|
|
||||||
|
p.setBrush(Qt::cyan); |
||||||
|
|
||||||
|
uint64_t ct = unlogger->getCurrentTime(); |
||||||
|
if (ct != 0) { |
||||||
|
addSegment((((ct-t0)*1e-9)/60)+1); |
||||||
|
int rrt = timeToPixel(ct-t0); |
||||||
|
p.drawRect(rrt-1, 0, 2, 600); |
||||||
|
|
||||||
|
timeLE->setText(QString("%1").arg((ct-t0)*1e-9, '8', 'f', 2)); |
||||||
|
} |
||||||
|
|
||||||
|
p.end(); |
||||||
|
|
||||||
|
if (timer.elapsed() > 50) { |
||||||
|
qDebug() << "paint in" << timer.elapsed() << "ms"; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
int main(int argc, char *argv[]) { |
||||||
|
QApplication app(argc, argv); |
||||||
|
|
||||||
|
QString route(argv[1]); |
||||||
|
int seek = QString(argv[2]).toInt(); |
||||||
|
printf("seek: %d\n", seek); |
||||||
|
route = route.replace("|", "/"); |
||||||
|
if (route == "") { |
||||||
|
printf("usage %s: <route>\n", argv[0]); |
||||||
|
exit(0); |
||||||
|
//route = "3a5d6ac1c23e5536/2019-10-29--10-06-58";
|
||||||
|
//route = "0006c839f32a6f99/2019-02-18--06-21-29";
|
||||||
|
//route = "02ec6bea180a4d36/2019-10-25--10-18-09";
|
||||||
|
} |
||||||
|
|
||||||
|
Window window(route, seek); |
||||||
|
window.resize(1920, 800); |
||||||
|
window.setWindowTitle("nui unlogger"); |
||||||
|
window.show(); |
||||||
|
|
||||||
|
return app.exec(); |
||||||
|
} |
||||||
|
|
@ -0,0 +1,37 @@ |
|||||||
|
###################################################################### |
||||||
|
# Automatically generated by qmake (3.0) Tue Oct 29 11:15:25 2019 |
||||||
|
###################################################################### |
||||||
|
|
||||||
|
TEMPLATE = app |
||||||
|
TARGET = nui |
||||||
|
INCLUDEPATH += . |
||||||
|
|
||||||
|
# Input |
||||||
|
SOURCES += main.cpp FileReader.cpp Unlogger.cpp ../clib/FrameReader.cpp |
||||||
|
HEADERS = FileReader.hpp Unlogger.hpp ../clib/FrameReader.hpp |
||||||
|
|
||||||
|
CONFIG += c++14 |
||||||
|
CONFIG += debug |
||||||
|
|
||||||
|
QT += widgets network core |
||||||
|
|
||||||
|
BASEDIR = "../../" |
||||||
|
PHONELIBS = $$BASEDIR"phonelibs" |
||||||
|
|
||||||
|
INCLUDEPATH = $$PHONELIBS/capnp-cpp/include $$PHONELIBS/yaml-cpp/include ../clib/ |
||||||
|
|
||||||
|
unix:!macx { |
||||||
|
LIBS += -L$$PHONELIBS/capnp-cpp/x64/lib -L$$PHONELIBS/yaml-cpp/x64/lib -Wl,-rpath=$$PHONELIBS/capnp-cpp/x64/lib |
||||||
|
} |
||||||
|
|
||||||
|
macx: { |
||||||
|
LIBS += -L$$PHONELIBS/capnp-cpp/mac/lib -L$$PHONELIBS/yaml-cpp/mac/lib |
||||||
|
} |
||||||
|
|
||||||
|
LIBS += -lcapnp -lkj -lyaml-cpp |
||||||
|
|
||||||
|
INCLUDEPATH += /usr/local/include |
||||||
|
INCLUDEPATH += $$PHONELIBS/capnp-cpp/include $$BASEDIR $$BASEDIR/cereal/messaging $$PHONELIBS/yaml-cpp/include |
||||||
|
LIBS += -L/usr/local/lib -lavformat -lavcodec -lavutil -lswscale |
||||||
|
LIBS += -lbz2 $$BASEDIR/cereal/libmessaging.a -lzmq |
||||||
|
|
@ -0,0 +1 @@ |
|||||||
|
test |
@ -0,0 +1,14 @@ |
|||||||
|
#include "FrameReader.hpp" |
||||||
|
#include "TestFrameReader.hpp" |
||||||
|
|
||||||
|
void TestFrameReader::frameread() { |
||||||
|
QElapsedTimer t; |
||||||
|
t.start(); |
||||||
|
FrameReader fr("3a5d6ac1c23e5536/2019-10-29--10-06-58/2/fcamera.hevc"); |
||||||
|
fr.get(2); |
||||||
|
//QThread::sleep(10);
|
||||||
|
qDebug() << t.nsecsElapsed()*1e-9 << "seconds"; |
||||||
|
} |
||||||
|
|
||||||
|
QTEST_MAIN(TestFrameReader) |
||||||
|
|
@ -0,0 +1,8 @@ |
|||||||
|
#include <QtTest/QtTest> |
||||||
|
|
||||||
|
class TestFrameReader : public QObject { |
||||||
|
Q_OBJECT |
||||||
|
private slots: |
||||||
|
void frameread(); |
||||||
|
}; |
||||||
|
|
@ -0,0 +1,16 @@ |
|||||||
|
###################################################################### |
||||||
|
# Automatically generated by qmake (3.0) Thu Oct 31 16:05:48 2019 |
||||||
|
###################################################################### |
||||||
|
|
||||||
|
QT += testlib |
||||||
|
TEMPLATE = app |
||||||
|
TARGET = test |
||||||
|
INCLUDEPATH += . ../ |
||||||
|
|
||||||
|
# Input |
||||||
|
SOURCES += TestFrameReader.cpp ../FrameReader.cpp |
||||||
|
HEADERS = TestFrameReader.hpp ../FrameReader.hpp |
||||||
|
|
||||||
|
CONFIG += c++14 |
||||||
|
|
||||||
|
LIBS += -lavformat -lavcodec -lavutil -lswscale |
@ -0,0 +1,112 @@ |
|||||||
|
#!/usr/bin/env python |
||||||
|
import os |
||||||
|
|
||||||
|
from common.basedir import BASEDIR |
||||||
|
os.environ['BASEDIR'] = BASEDIR |
||||||
|
SCALE = 3 |
||||||
|
|
||||||
|
import argparse |
||||||
|
import zmq |
||||||
|
import pygame |
||||||
|
import numpy as np |
||||||
|
import cv2 |
||||||
|
import sys |
||||||
|
import traceback |
||||||
|
from collections import namedtuple |
||||||
|
from cereal import car |
||||||
|
from common.params import Params |
||||||
|
from tools.lib.lazy_property import lazy_property |
||||||
|
from cereal.messaging import sub_sock, recv_one_or_none, recv_one |
||||||
|
from cereal.services import service_list |
||||||
|
|
||||||
|
_BB_OFFSET = 0, 0 |
||||||
|
_BB_TO_FULL_FRAME = np.asarray([[1., 0., _BB_OFFSET[0]], [0., 1., _BB_OFFSET[1]], |
||||||
|
[0., 0., 1.]]) |
||||||
|
_FULL_FRAME_TO_BB = np.linalg.inv(_BB_TO_FULL_FRAME) |
||||||
|
_FULL_FRAME_SIZE = 1164, 874 |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def pygame_modules_have_loaded(): |
||||||
|
return pygame.display.get_init() and pygame.font.get_init() |
||||||
|
|
||||||
|
|
||||||
|
def ui_thread(addr, frame_address): |
||||||
|
context = zmq.Context.instance() |
||||||
|
|
||||||
|
pygame.init() |
||||||
|
pygame.font.init() |
||||||
|
assert pygame_modules_have_loaded() |
||||||
|
|
||||||
|
size = (_FULL_FRAME_SIZE[0] * SCALE, _FULL_FRAME_SIZE[1] * SCALE) |
||||||
|
pygame.display.set_caption("comma one debug UI") |
||||||
|
screen = pygame.display.set_mode(size, pygame.DOUBLEBUF) |
||||||
|
|
||||||
|
camera_surface = pygame.surface.Surface((_FULL_FRAME_SIZE[0] * SCALE, _FULL_FRAME_SIZE[1] * SCALE), 0, 24).convert() |
||||||
|
|
||||||
|
frame = context.socket(zmq.SUB) |
||||||
|
frame.connect(frame_address or "tcp://%s:%d" % (addr, service_list['frame'].port)) |
||||||
|
frame.setsockopt(zmq.SUBSCRIBE, "") |
||||||
|
|
||||||
|
img = np.zeros((_FULL_FRAME_SIZE[1], _FULL_FRAME_SIZE[0], 3), dtype='uint8') |
||||||
|
imgff = np.zeros((_FULL_FRAME_SIZE[1], _FULL_FRAME_SIZE[0], 3), dtype=np.uint8) |
||||||
|
|
||||||
|
while 1: |
||||||
|
list(pygame.event.get()) |
||||||
|
screen.fill((64, 64, 64)) |
||||||
|
|
||||||
|
# ***** frame ***** |
||||||
|
fpkt = recv_one(frame) |
||||||
|
yuv_img = fpkt.frame.image |
||||||
|
|
||||||
|
if fpkt.frame.transform: |
||||||
|
yuv_transform = np.array(fpkt.frame.transform).reshape(3, 3) |
||||||
|
else: |
||||||
|
# assume frame is flipped |
||||||
|
yuv_transform = np.array([[-1.0, 0.0, _FULL_FRAME_SIZE[0] - 1], |
||||||
|
[0.0, -1.0, _FULL_FRAME_SIZE[1] - 1], [0.0, 0.0, 1.0]]) |
||||||
|
|
||||||
|
if yuv_img and len(yuv_img) == _FULL_FRAME_SIZE[0] * _FULL_FRAME_SIZE[1] * 3 // 2: |
||||||
|
yuv_np = np.frombuffer( |
||||||
|
yuv_img, dtype=np.uint8).reshape(_FULL_FRAME_SIZE[1] * 3 // 2, -1) |
||||||
|
cv2.cvtColor(yuv_np, cv2.COLOR_YUV2RGB_I420, dst=imgff) |
||||||
|
cv2.warpAffine( |
||||||
|
imgff, |
||||||
|
np.dot(yuv_transform, _BB_TO_FULL_FRAME)[:2], (img.shape[1], img.shape[0]), |
||||||
|
dst=img, |
||||||
|
flags=cv2.WARP_INVERSE_MAP) |
||||||
|
else: |
||||||
|
img.fill(0) |
||||||
|
|
||||||
|
height, width = img.shape[:2] |
||||||
|
img_resized = cv2.resize( |
||||||
|
img, (SCALE * width, SCALE * height), interpolation=cv2.INTER_CUBIC) |
||||||
|
# *** blits *** |
||||||
|
pygame.surfarray.blit_array(camera_surface, img_resized.swapaxes(0, 1)) |
||||||
|
screen.blit(camera_surface, (0, 0)) |
||||||
|
|
||||||
|
# this takes time...vsync or something |
||||||
|
pygame.display.flip() |
||||||
|
|
||||||
|
|
||||||
|
def get_arg_parser(): |
||||||
|
parser = argparse.ArgumentParser( |
||||||
|
description="Show replay data in a UI.", |
||||||
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter) |
||||||
|
|
||||||
|
parser.add_argument( |
||||||
|
"ip_address", |
||||||
|
nargs="?", |
||||||
|
default="127.0.0.1", |
||||||
|
help="The ip address on which to receive zmq messages.") |
||||||
|
|
||||||
|
parser.add_argument( |
||||||
|
"--frame-address", |
||||||
|
default=None, |
||||||
|
help="The ip address on which to receive zmq messages.") |
||||||
|
return parser |
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__": |
||||||
|
args = get_arg_parser().parse_args(sys.argv[1:]) |
||||||
|
ui_thread(args.ip_address, args.frame_address) |
@ -0,0 +1,314 @@ |
|||||||
|
import platform |
||||||
|
from collections import namedtuple |
||||||
|
|
||||||
|
import matplotlib |
||||||
|
import matplotlib.pyplot as plt |
||||||
|
import numpy as np |
||||||
|
import pygame |
||||||
|
|
||||||
|
from tools.lib.lazy_property import lazy_property |
||||||
|
from selfdrive.config import UIParams as UP |
||||||
|
from selfdrive.config import RADAR_TO_CAMERA |
||||||
|
from selfdrive.controls.lib.lane_planner import (compute_path_pinv, |
||||||
|
model_polyfit) |
||||||
|
|
||||||
|
RED = (255, 0, 0) |
||||||
|
GREEN = (0, 255, 0) |
||||||
|
BLUE = (0, 0, 255) |
||||||
|
YELLOW = (255, 255, 0) |
||||||
|
BLACK = (0, 0, 0) |
||||||
|
WHITE = (255, 255, 255) |
||||||
|
|
||||||
|
_PATH_X = np.arange(192.) |
||||||
|
_PATH_XD = np.arange(192.) |
||||||
|
_PATH_PINV = compute_path_pinv(50) |
||||||
|
#_BB_OFFSET = 290, 332 |
||||||
|
_BB_OFFSET = 0,0 |
||||||
|
_BB_SCALE = 1164/640. |
||||||
|
_BB_TO_FULL_FRAME = np.asarray([ |
||||||
|
[_BB_SCALE, 0., _BB_OFFSET[0]], |
||||||
|
[0., _BB_SCALE, _BB_OFFSET[1]], |
||||||
|
[0., 0., 1.]]) |
||||||
|
_FULL_FRAME_TO_BB = np.linalg.inv(_BB_TO_FULL_FRAME) |
||||||
|
|
||||||
|
METER_WIDTH = 20 |
||||||
|
|
||||||
|
ModelUIData = namedtuple("ModelUIData", ["cpath", "lpath", "rpath", "lead", "lead_future"]) |
||||||
|
|
||||||
|
_COLOR_CACHE = {} |
||||||
|
def find_color(lidar_surface, color): |
||||||
|
if color in _COLOR_CACHE: |
||||||
|
return _COLOR_CACHE[color] |
||||||
|
tcolor = 0 |
||||||
|
ret = 255 |
||||||
|
for x in lidar_surface.get_palette(): |
||||||
|
#print tcolor, x |
||||||
|
if x[0:3] == color: |
||||||
|
ret = tcolor |
||||||
|
break |
||||||
|
tcolor += 1 |
||||||
|
_COLOR_CACHE[color] = ret |
||||||
|
return ret |
||||||
|
|
||||||
|
def warp_points(pt_s, warp_matrix): |
||||||
|
# pt_s are the source points, nxm array. |
||||||
|
pt_d = np.dot(warp_matrix[:, :-1], pt_s.T) + warp_matrix[:, -1, None] |
||||||
|
|
||||||
|
# Divide by last dimension for representation in image space. |
||||||
|
return (pt_d[:-1, :] / pt_d[-1, :]).T |
||||||
|
|
||||||
|
def to_lid_pt(y, x): |
||||||
|
px, py = -x * UP.lidar_zoom + UP.lidar_car_x, -y * UP.lidar_zoom + UP.lidar_car_y |
||||||
|
if px > 0 and py > 0 and px < UP.lidar_x and py < UP.lidar_y: |
||||||
|
return int(px), int(py) |
||||||
|
return -1, -1 |
||||||
|
|
||||||
|
|
||||||
|
def draw_path(y, x, color, img, calibration, top_down, lid_color=None): |
||||||
|
# TODO: Remove big box. |
||||||
|
uv_model_real = warp_points(np.column_stack((x, y)), calibration.car_to_model) |
||||||
|
uv_model = np.round(uv_model_real).astype(int) |
||||||
|
|
||||||
|
uv_model_dots = uv_model[np.logical_and.reduce((np.all( # pylint: disable=no-member |
||||||
|
uv_model > 0, axis=1), uv_model[:, 0] < img.shape[1] - 1, uv_model[:, 1] < |
||||||
|
img.shape[0] - 1))] |
||||||
|
|
||||||
|
for i, j in ((-1, 0), (0, -1), (0, 0), (0, 1), (1, 0)): |
||||||
|
img[uv_model_dots[:, 1] + i, uv_model_dots[:, 0] + j] = color |
||||||
|
|
||||||
|
# draw lidar path point on lidar |
||||||
|
# find color in 8 bit |
||||||
|
if lid_color is not None and top_down is not None: |
||||||
|
tcolor = find_color(top_down[0], lid_color) |
||||||
|
for i in range(len(x)): |
||||||
|
px, py = to_lid_pt(x[i], y[i]) |
||||||
|
if px != -1: |
||||||
|
top_down[1][px, py] = tcolor |
||||||
|
|
||||||
|
def draw_steer_path(speed_ms, curvature, color, img, |
||||||
|
calibration, top_down, VM, lid_color=None): |
||||||
|
path_x = np.arange(101.) |
||||||
|
path_y = np.multiply(path_x, np.tan(np.arcsin(np.clip(path_x * curvature, -0.999, 0.999)) / 2.)) |
||||||
|
|
||||||
|
draw_path(path_y, path_x, color, img, calibration, top_down, lid_color) |
||||||
|
|
||||||
|
def draw_lead_car(closest, top_down): |
||||||
|
if closest != None: |
||||||
|
closest_y = int(round(UP.lidar_car_y - closest * UP.lidar_zoom)) |
||||||
|
if closest_y > 0: |
||||||
|
top_down[1][int(round(UP.lidar_car_x - METER_WIDTH * 2)):int( |
||||||
|
round(UP.lidar_car_x + METER_WIDTH * 2)), closest_y] = find_color( |
||||||
|
top_down[0], (255, 0, 0)) |
||||||
|
|
||||||
|
def draw_lead_on(img, closest_x_m, closest_y_m, calibration, color, sz=10, img_offset=(0, 0)): |
||||||
|
uv = warp_points(np.asarray([closest_x_m, closest_y_m]), calibration.car_to_bb)[0] |
||||||
|
u, v = int(uv[0] + img_offset[0]), int(uv[1] + img_offset[1]) |
||||||
|
if u > 0 and u < 640 and v > 0 and v < 480 - 5: |
||||||
|
img[v - 5 - sz:v - 5 + sz, u] = color |
||||||
|
img[v - 5, u - sz:u + sz] = color |
||||||
|
return u, v |
||||||
|
|
||||||
|
|
||||||
|
if platform.system() != 'Darwin': |
||||||
|
matplotlib.use('QT4Agg') |
||||||
|
|
||||||
|
|
||||||
|
def init_plots(arr, name_to_arr_idx, plot_xlims, plot_ylims, plot_names, plot_colors, plot_styles, bigplots=False): |
||||||
|
color_palette = { "r": (1,0,0), |
||||||
|
"g": (0,1,0), |
||||||
|
"b": (0,0,1), |
||||||
|
"k": (0,0,0), |
||||||
|
"y": (1,1,0), |
||||||
|
"p": (0,1,1), |
||||||
|
"m": (1,0,1) } |
||||||
|
|
||||||
|
if bigplots == True: |
||||||
|
fig = plt.figure(figsize=(6.4, 7.0)) |
||||||
|
elif bigplots == False: |
||||||
|
fig = plt.figure() |
||||||
|
else: |
||||||
|
fig = plt.figure(figsize=bigplots) |
||||||
|
|
||||||
|
fig.set_facecolor((0.2,0.2,0.2)) |
||||||
|
|
||||||
|
axs = [] |
||||||
|
for pn in range(len(plot_ylims)): |
||||||
|
ax = fig.add_subplot(len(plot_ylims),1,len(axs)+1) |
||||||
|
ax.set_xlim(plot_xlims[pn][0], plot_xlims[pn][1]) |
||||||
|
ax.set_ylim(plot_ylims[pn][0], plot_ylims[pn][1]) |
||||||
|
ax.patch.set_facecolor((0.4, 0.4, 0.4)) |
||||||
|
axs.append(ax) |
||||||
|
|
||||||
|
plots = [] ;idxs = [] ;plot_select = [] |
||||||
|
for i, pl_list in enumerate(plot_names): |
||||||
|
for j, item in enumerate(pl_list): |
||||||
|
plot, = axs[i].plot(arr[:, name_to_arr_idx[item]], |
||||||
|
label=item, |
||||||
|
color=color_palette[plot_colors[i][j]], |
||||||
|
linestyle=plot_styles[i][j]) |
||||||
|
plots.append(plot) |
||||||
|
idxs.append(name_to_arr_idx[item]) |
||||||
|
plot_select.append(i) |
||||||
|
axs[i].set_title(", ".join("%s (%s)" % (nm, cl) |
||||||
|
for (nm, cl) in zip(pl_list, plot_colors[i])), fontsize=10) |
||||||
|
if i < len(plot_ylims) - 1: |
||||||
|
axs[i].set_xticks([]) |
||||||
|
|
||||||
|
fig.canvas.draw() |
||||||
|
|
||||||
|
renderer = fig.canvas.get_renderer() |
||||||
|
|
||||||
|
if matplotlib.get_backend() == "MacOSX": |
||||||
|
fig.draw(renderer) |
||||||
|
|
||||||
|
def draw_plots(arr): |
||||||
|
for ax in axs: |
||||||
|
ax.draw_artist(ax.patch) |
||||||
|
for i in range(len(plots)): |
||||||
|
plots[i].set_ydata(arr[:, idxs[i]]) |
||||||
|
axs[plot_select[i]].draw_artist(plots[i]) |
||||||
|
|
||||||
|
if matplotlib.get_backend() == "QT4Agg": |
||||||
|
fig.canvas.update() |
||||||
|
fig.canvas.flush_events() |
||||||
|
|
||||||
|
raw_data = renderer.tostring_rgb() |
||||||
|
#print fig.canvas.get_width_height() |
||||||
|
plot_surface = pygame.image.frombuffer(raw_data, fig.canvas.get_width_height(), "RGB").convert() |
||||||
|
return plot_surface |
||||||
|
|
||||||
|
return draw_plots |
||||||
|
|
||||||
|
|
||||||
|
def draw_mpc(liveMpc, top_down): |
||||||
|
mpc_color = find_color(top_down[0], (0, 255, 0)) |
||||||
|
for p in zip(liveMpc.x, liveMpc.y): |
||||||
|
px, py = to_lid_pt(*p) |
||||||
|
top_down[1][px, py] = mpc_color |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class CalibrationTransformsForWarpMatrix(object): |
||||||
|
def __init__(self, model_to_full_frame, K, E): |
||||||
|
self._model_to_full_frame = model_to_full_frame |
||||||
|
self._K = K |
||||||
|
self._E = E |
||||||
|
|
||||||
|
@property |
||||||
|
def model_to_bb(self): |
||||||
|
return _FULL_FRAME_TO_BB.dot(self._model_to_full_frame) |
||||||
|
|
||||||
|
@lazy_property |
||||||
|
def model_to_full_frame(self): |
||||||
|
return self._model_to_full_frame |
||||||
|
|
||||||
|
@lazy_property |
||||||
|
def car_to_model(self): |
||||||
|
return np.linalg.inv(self._model_to_full_frame).dot(self._K).dot( |
||||||
|
self._E[:, [0, 1, 3]]) |
||||||
|
|
||||||
|
@lazy_property |
||||||
|
def car_to_bb(self): |
||||||
|
return _BB_TO_FULL_FRAME.dot(self._K).dot(self._E[:, [0, 1, 3]]) |
||||||
|
|
||||||
|
|
||||||
|
def pygame_modules_have_loaded(): |
||||||
|
return pygame.display.get_init() and pygame.font.get_init() |
||||||
|
|
||||||
|
def draw_var(y, x, var, color, img, calibration, top_down): |
||||||
|
# otherwise drawing gets stupid |
||||||
|
var = max(1e-1, min(var, 0.7)) |
||||||
|
|
||||||
|
varcolor = tuple(np.array(color)*0.5) |
||||||
|
draw_path(y - var, x, varcolor, img, calibration, top_down) |
||||||
|
draw_path(y + var, x, varcolor, img, calibration, top_down) |
||||||
|
|
||||||
|
|
||||||
|
class ModelPoly(object): |
||||||
|
def __init__(self, model_path): |
||||||
|
if len(model_path.points) == 0 and len(model_path.poly) == 0: |
||||||
|
self.valid = False |
||||||
|
return |
||||||
|
|
||||||
|
if len(model_path.poly): |
||||||
|
self.poly = np.array(model_path.poly) |
||||||
|
else: |
||||||
|
self.poly = model_polyfit(model_path.points, _PATH_PINV) |
||||||
|
|
||||||
|
self.prob = model_path.prob |
||||||
|
self.std = model_path.std |
||||||
|
self.y = np.polyval(self.poly, _PATH_XD) |
||||||
|
self.valid = True |
||||||
|
|
||||||
|
def extract_model_data(md): |
||||||
|
return ModelUIData( |
||||||
|
cpath=ModelPoly(md.path), |
||||||
|
lpath=ModelPoly(md.leftLane), |
||||||
|
rpath=ModelPoly(md.rightLane), |
||||||
|
lead=md.lead, |
||||||
|
lead_future=md.leadFuture, |
||||||
|
) |
||||||
|
|
||||||
|
def plot_model(m, VM, v_ego, curvature, imgw, calibration, top_down, d_poly, top_down_color=216): |
||||||
|
if calibration is None or top_down is None: |
||||||
|
return |
||||||
|
|
||||||
|
for lead in [m.lead, m.lead_future]: |
||||||
|
if lead.prob < 0.5: |
||||||
|
continue |
||||||
|
|
||||||
|
lead_dist_from_radar = lead.dist - RADAR_TO_CAMERA |
||||||
|
_, py_top = to_lid_pt(lead_dist_from_radar + lead.std, lead.relY) |
||||||
|
px, py_bottom = to_lid_pt(lead_dist_from_radar - lead.std, lead.relY) |
||||||
|
top_down[1][int(round(px - 4)):int(round(px + 4)), py_top:py_bottom] = top_down_color |
||||||
|
|
||||||
|
color = (0, int(255 * m.lpath.prob), 0) |
||||||
|
for path in [m.cpath, m.lpath, m.rpath]: |
||||||
|
if path.valid: |
||||||
|
draw_path(path.y, _PATH_XD, color, imgw, calibration, top_down, YELLOW) |
||||||
|
draw_var(path.y, _PATH_XD, path.std, color, imgw, calibration, top_down) |
||||||
|
|
||||||
|
if d_poly is not None: |
||||||
|
dpath_y = np.polyval(d_poly, _PATH_X) |
||||||
|
draw_path(dpath_y, _PATH_X, RED, imgw, calibration, top_down, RED) |
||||||
|
|
||||||
|
# draw user path from curvature |
||||||
|
draw_steer_path(v_ego, curvature, BLUE, imgw, calibration, top_down, VM, BLUE) |
||||||
|
|
||||||
|
|
||||||
|
def maybe_update_radar_points(lt, lid_overlay): |
||||||
|
ar_pts = [] |
||||||
|
if lt is not None: |
||||||
|
ar_pts = {} |
||||||
|
for track in lt: |
||||||
|
ar_pts[track.trackId] = [track.dRel, track.yRel, track.vRel, track.aRel, track.oncoming, track.stationary] |
||||||
|
for ids, pt in ar_pts.items(): |
||||||
|
px, py = to_lid_pt(pt[0], pt[1]) |
||||||
|
if px != -1: |
||||||
|
if pt[-1]: |
||||||
|
color = 240 |
||||||
|
elif pt[-2]: |
||||||
|
color = 230 |
||||||
|
else: |
||||||
|
color = 255 |
||||||
|
if int(ids) == 1: |
||||||
|
lid_overlay[px - 2:px + 2, py - 10:py + 10] = 100 |
||||||
|
else: |
||||||
|
lid_overlay[px - 2:px + 2, py - 2:py + 2] = color |
||||||
|
|
||||||
|
def get_blank_lid_overlay(UP): |
||||||
|
lid_overlay = np.zeros((UP.lidar_x, UP.lidar_y), 'uint8') |
||||||
|
# Draw the car. |
||||||
|
lid_overlay[int(round(UP.lidar_car_x - UP.car_hwidth)):int( |
||||||
|
round(UP.lidar_car_x + UP.car_hwidth)), int(round(UP.lidar_car_y - |
||||||
|
UP.car_front))] = UP.car_color |
||||||
|
lid_overlay[int(round(UP.lidar_car_x - UP.car_hwidth)):int( |
||||||
|
round(UP.lidar_car_x + UP.car_hwidth)), int(round(UP.lidar_car_y + |
||||||
|
UP.car_back))] = UP.car_color |
||||||
|
lid_overlay[int(round(UP.lidar_car_x - UP.car_hwidth)), int( |
||||||
|
round(UP.lidar_car_y - UP.car_front)):int(round( |
||||||
|
UP.lidar_car_y + UP.car_back))] = UP.car_color |
||||||
|
lid_overlay[int(round(UP.lidar_car_x + UP.car_hwidth)), int( |
||||||
|
round(UP.lidar_car_y - UP.car_front)):int(round( |
||||||
|
UP.lidar_car_y + UP.car_back))] = UP.car_color |
||||||
|
return lid_overlay |
@ -0,0 +1,68 @@ |
|||||||
|
#!/usr/bin/env python |
||||||
|
|
||||||
|
import matplotlib |
||||||
|
matplotlib.use('TkAgg') |
||||||
|
import matplotlib.pyplot as plt |
||||||
|
|
||||||
|
import numpy as np |
||||||
|
import zmq |
||||||
|
from cereal.services import service_list |
||||||
|
from selfdrive.config import Conversions as CV |
||||||
|
import cereal.messaging as messaging |
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__": |
||||||
|
live_map_sock = messaging.sub_sock(service_list['liveMapData'].port, conflate=True) |
||||||
|
plan_sock = messaging.sub_sock(service_list['plan'].port, conflate=True) |
||||||
|
|
||||||
|
plt.ion() |
||||||
|
fig = plt.figure(figsize=(8, 16)) |
||||||
|
ax = fig.add_subplot(2, 1, 1) |
||||||
|
ax.set_title('Map') |
||||||
|
|
||||||
|
SCALE = 1000 |
||||||
|
ax.set_xlim([-SCALE, SCALE]) |
||||||
|
ax.set_ylim([-SCALE, SCALE]) |
||||||
|
ax.set_xlabel('x [m]') |
||||||
|
ax.set_ylabel('y [m]') |
||||||
|
ax.grid(True) |
||||||
|
|
||||||
|
points_plt, = ax.plot([0.0], [0.0], "--xk") |
||||||
|
cur, = ax.plot([0.0], [0.0], "xr") |
||||||
|
|
||||||
|
speed_txt = ax.text(-500, 900, '') |
||||||
|
curv_txt = ax.text(-500, 775, '') |
||||||
|
|
||||||
|
ax = fig.add_subplot(2, 1, 2) |
||||||
|
ax.set_title('Curvature') |
||||||
|
curvature_plt, = ax.plot([0.0], [0.0], "--xk") |
||||||
|
ax.set_xlim([0, 500]) |
||||||
|
ax.set_ylim([0, 1e-2]) |
||||||
|
ax.set_xlabel('Distance along path [m]') |
||||||
|
ax.set_ylabel('Curvature [1/m]') |
||||||
|
ax.grid(True) |
||||||
|
|
||||||
|
plt.show() |
||||||
|
|
||||||
|
while True: |
||||||
|
m = messaging.recv_one_or_none(live_map_sock) |
||||||
|
p = messaging.recv_one_or_none(plan_sock) |
||||||
|
if p is not None: |
||||||
|
v = p.plan.vCurvature * CV.MS_TO_MPH |
||||||
|
speed_txt.set_text('Desired curvature speed: %.2f mph' % v) |
||||||
|
|
||||||
|
if m is not None: |
||||||
|
print("Current way id: %d" % m.liveMapData.wayId) |
||||||
|
curv_txt.set_text('Curvature valid: %s Dist: %03.0f m\nSpeedlimit valid: %s Speed: %.0f mph' % |
||||||
|
(str(m.liveMapData.curvatureValid), |
||||||
|
m.liveMapData.distToTurn, |
||||||
|
str(m.liveMapData.speedLimitValid), |
||||||
|
m.liveMapData.speedLimit * CV.MS_TO_MPH)) |
||||||
|
|
||||||
|
points_plt.set_xdata(m.liveMapData.roadX) |
||||||
|
points_plt.set_ydata(m.liveMapData.roadY) |
||||||
|
curvature_plt.set_xdata(m.liveMapData.roadCurvatureX) |
||||||
|
curvature_plt.set_ydata(m.liveMapData.roadCurvature) |
||||||
|
|
||||||
|
fig.canvas.draw() |
||||||
|
fig.canvas.flush_events() |
@ -0,0 +1,83 @@ |
|||||||
|
#!/usr/bin/env python |
||||||
|
import os |
||||||
|
import sys |
||||||
|
import matplotlib.pyplot as plt |
||||||
|
import numpy as np |
||||||
|
import cereal.messaging as messaging |
||||||
|
import time |
||||||
|
|
||||||
|
|
||||||
|
# tool to plot one or more signals live. Call ex: |
||||||
|
#./rqplot.py log.carState.vEgo log.carState.aEgo |
||||||
|
|
||||||
|
# TODO: can this tool consume 10x less cpu? |
||||||
|
|
||||||
|
def recursive_getattr(x, name): |
||||||
|
l = name.split('.') |
||||||
|
if len(l) == 1: |
||||||
|
return getattr(x, name) |
||||||
|
else: |
||||||
|
return recursive_getattr(getattr(x, l[0]), ".".join(l[1:]) ) |
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__": |
||||||
|
poller = messaging.Poller() |
||||||
|
|
||||||
|
services = [] |
||||||
|
fields = [] |
||||||
|
subs = [] |
||||||
|
values = [] |
||||||
|
|
||||||
|
plt.ion() |
||||||
|
fig, ax = plt.subplots() |
||||||
|
#fig = plt.figure(figsize=(10, 15)) |
||||||
|
#ax = fig.add_subplot(111) |
||||||
|
ax.grid(True) |
||||||
|
fig.canvas.draw() |
||||||
|
|
||||||
|
subs_name = sys.argv[1:] |
||||||
|
lines = [] |
||||||
|
x, y = [], [] |
||||||
|
LEN = 500 |
||||||
|
|
||||||
|
for i, sub in enumerate(subs_name): |
||||||
|
sub_split = sub.split(".") |
||||||
|
services.append(sub_split[0]) |
||||||
|
fields.append(".".join(sub_split[1:])) |
||||||
|
subs.append(messaging.sub_sock(sub_split[0], poller)) |
||||||
|
|
||||||
|
x.append(np.ones(LEN)*np.nan) |
||||||
|
y.append(np.ones(LEN)*np.nan) |
||||||
|
lines.append(ax.plot(x[i], y[i])[0]) |
||||||
|
|
||||||
|
for l in lines: |
||||||
|
l.set_marker("*") |
||||||
|
|
||||||
|
cur_t = 0. |
||||||
|
ax.legend(subs_name) |
||||||
|
ax.set_xlabel('time [s]') |
||||||
|
|
||||||
|
while 1: |
||||||
|
print(1./(time.time() - cur_t)) |
||||||
|
cur_t = time.time() |
||||||
|
for i, s in enumerate(subs): |
||||||
|
msg = messaging.recv_sock(s) |
||||||
|
#msg = messaging.recv_one_or_none(s) |
||||||
|
if msg is not None: |
||||||
|
x[i] = np.append(x[i], getattr(msg, 'logMonoTime') / float(1e9)) |
||||||
|
x[i] = np.delete(x[i], 0) |
||||||
|
y[i] = np.append(y[i], recursive_getattr(msg, subs_name[i])) |
||||||
|
y[i] = np.delete(y[i], 0) |
||||||
|
|
||||||
|
lines[i].set_xdata(x[i]) |
||||||
|
lines[i].set_ydata(y[i]) |
||||||
|
|
||||||
|
ax.relim() |
||||||
|
ax.autoscale_view(True, scaley=True, scalex=True) |
||||||
|
|
||||||
|
fig.canvas.blit(ax.bbox) |
||||||
|
fig.canvas.flush_events() |
||||||
|
|
||||||
|
# just a bit of wait to avoid 100% CPU usage |
||||||
|
time.sleep(0.001) |
||||||
|
|
@ -0,0 +1,278 @@ |
|||||||
|
#!/usr/bin/env python |
||||||
|
import argparse |
||||||
|
import os |
||||||
|
import sys |
||||||
|
|
||||||
|
os.environ["OMP_NUM_THREADS"] = "1" |
||||||
|
|
||||||
|
import cv2 |
||||||
|
import numpy as np |
||||||
|
import pygame |
||||||
|
|
||||||
|
from common.basedir import BASEDIR |
||||||
|
from common.transformations.camera import FULL_FRAME_SIZE, eon_intrinsics |
||||||
|
from common.transformations.model import (MODEL_CX, MODEL_CY, MODEL_INPUT_SIZE, |
||||||
|
get_camera_frame_from_model_frame) |
||||||
|
from selfdrive.car.toyota.interface import CarInterface as ToyotaInterface |
||||||
|
from selfdrive.config import UIParams as UP |
||||||
|
from selfdrive.controls.lib.vehicle_model import VehicleModel |
||||||
|
import cereal.messaging as messaging |
||||||
|
from tools.replay.lib.ui_helpers import (_BB_TO_FULL_FRAME, BLACK, BLUE, GREEN, |
||||||
|
YELLOW, RED, |
||||||
|
CalibrationTransformsForWarpMatrix, |
||||||
|
draw_lead_car, draw_lead_on, draw_mpc, |
||||||
|
extract_model_data, |
||||||
|
get_blank_lid_overlay, init_plots, |
||||||
|
maybe_update_radar_points, plot_model, |
||||||
|
pygame_modules_have_loaded, |
||||||
|
warp_points) |
||||||
|
|
||||||
|
os.environ['BASEDIR'] = BASEDIR |
||||||
|
|
||||||
|
ANGLE_SCALE = 5.0 |
||||||
|
HOR = os.getenv("HORIZONTAL") is not None |
||||||
|
|
||||||
|
|
||||||
|
def ui_thread(addr, frame_address): |
||||||
|
# TODO: Detect car from replay and use that to select carparams |
||||||
|
CP = ToyotaInterface.get_params("TOYOTA PRIUS 2017") |
||||||
|
VM = VehicleModel(CP) |
||||||
|
|
||||||
|
CalP = np.asarray([[0, 0], [MODEL_INPUT_SIZE[0], 0], [MODEL_INPUT_SIZE[0], MODEL_INPUT_SIZE[1]], [0, MODEL_INPUT_SIZE[1]]]) |
||||||
|
vanishing_point = np.asarray([[MODEL_CX, MODEL_CY]]) |
||||||
|
|
||||||
|
pygame.init() |
||||||
|
pygame.font.init() |
||||||
|
assert pygame_modules_have_loaded() |
||||||
|
|
||||||
|
if HOR: |
||||||
|
size = (640+384+640, 960) |
||||||
|
write_x = 5 |
||||||
|
write_y = 680 |
||||||
|
else: |
||||||
|
size = (640+384, 960+300) |
||||||
|
write_x = 645 |
||||||
|
write_y = 970 |
||||||
|
|
||||||
|
pygame.display.set_caption("openpilot debug UI") |
||||||
|
screen = pygame.display.set_mode(size, pygame.DOUBLEBUF) |
||||||
|
|
||||||
|
alert1_font = pygame.font.SysFont("arial", 30) |
||||||
|
alert2_font = pygame.font.SysFont("arial", 20) |
||||||
|
info_font = pygame.font.SysFont("arial", 15) |
||||||
|
|
||||||
|
camera_surface = pygame.surface.Surface((640, 480), 0, 24).convert() |
||||||
|
cameraw_surface = pygame.surface.Surface(MODEL_INPUT_SIZE, 0, 24).convert() |
||||||
|
cameraw_test_surface = pygame.surface.Surface(MODEL_INPUT_SIZE, 0, 24) |
||||||
|
top_down_surface = pygame.surface.Surface((UP.lidar_x, UP.lidar_y),0,8) |
||||||
|
|
||||||
|
frame = messaging.sub_sock('frame', addr=addr, conflate=True) |
||||||
|
sm = messaging.SubMaster(['carState', 'plan', 'carControl', 'radarState', 'liveCalibration', 'controlsState', 'liveTracks', 'model', 'liveMpc', 'liveParameters', 'pathPlan'], addr=addr) |
||||||
|
|
||||||
|
calibration = None |
||||||
|
img = np.zeros((480, 640, 3), dtype='uint8') |
||||||
|
imgff = np.zeros((FULL_FRAME_SIZE[1], FULL_FRAME_SIZE[0], 3), dtype=np.uint8) |
||||||
|
imgw = np.zeros((160, 320, 3), dtype=np.uint8) # warped image |
||||||
|
lid_overlay_blank = get_blank_lid_overlay(UP) |
||||||
|
|
||||||
|
# plots |
||||||
|
name_to_arr_idx = { "gas": 0, |
||||||
|
"computer_gas": 1, |
||||||
|
"user_brake": 2, |
||||||
|
"computer_brake": 3, |
||||||
|
"v_ego": 4, |
||||||
|
"v_pid": 5, |
||||||
|
"angle_steers_des": 6, |
||||||
|
"angle_steers": 7, |
||||||
|
"angle_steers_k": 8, |
||||||
|
"steer_torque": 9, |
||||||
|
"v_override": 10, |
||||||
|
"v_cruise": 11, |
||||||
|
"a_ego": 12, |
||||||
|
"a_target": 13, |
||||||
|
"accel_override": 14} |
||||||
|
|
||||||
|
plot_arr = np.zeros((100, len(name_to_arr_idx.values()))) |
||||||
|
|
||||||
|
plot_xlims = [(0, plot_arr.shape[0]), (0, plot_arr.shape[0]), (0, plot_arr.shape[0]), (0, plot_arr.shape[0])] |
||||||
|
plot_ylims = [(-0.1, 1.1), (-ANGLE_SCALE, ANGLE_SCALE), (0., 75.), (-3.0, 2.0)] |
||||||
|
plot_names = [["gas", "computer_gas", "user_brake", "computer_brake", "accel_override"], |
||||||
|
["angle_steers", "angle_steers_des", "angle_steers_k", "steer_torque"], |
||||||
|
["v_ego", "v_override", "v_pid", "v_cruise"], |
||||||
|
["a_ego", "a_target"]] |
||||||
|
plot_colors = [["b", "b", "g", "r", "y"], |
||||||
|
["b", "g", "y", "r"], |
||||||
|
["b", "g", "r", "y"], |
||||||
|
["b", "r"]] |
||||||
|
plot_styles = [["-", "-", "-", "-", "-"], |
||||||
|
["-", "-", "-", "-"], |
||||||
|
["-", "-", "-", "-"], |
||||||
|
["-", "-"]] |
||||||
|
|
||||||
|
draw_plots = init_plots(plot_arr, name_to_arr_idx, plot_xlims, plot_ylims, plot_names, plot_colors, plot_styles, bigplots=True) |
||||||
|
|
||||||
|
counter = 0 |
||||||
|
while 1: |
||||||
|
list(pygame.event.get()) |
||||||
|
|
||||||
|
screen.fill((64,64,64)) |
||||||
|
lid_overlay = lid_overlay_blank.copy() |
||||||
|
top_down = top_down_surface, lid_overlay |
||||||
|
|
||||||
|
# ***** frame ***** |
||||||
|
fpkt = messaging.recv_one(frame) |
||||||
|
rgb_img_raw = fpkt.frame.image |
||||||
|
|
||||||
|
if fpkt.frame.transform: |
||||||
|
img_transform = np.array(fpkt.frame.transform).reshape(3,3) |
||||||
|
else: |
||||||
|
# assume frame is flipped |
||||||
|
img_transform = np.array([ |
||||||
|
[-1.0, 0.0, FULL_FRAME_SIZE[0]-1], |
||||||
|
[ 0.0, -1.0, FULL_FRAME_SIZE[1]-1], |
||||||
|
[ 0.0, 0.0, 1.0] |
||||||
|
]) |
||||||
|
|
||||||
|
|
||||||
|
if rgb_img_raw and len(rgb_img_raw) == FULL_FRAME_SIZE[0] * FULL_FRAME_SIZE[1] * 3: |
||||||
|
imgff = np.frombuffer(rgb_img_raw, dtype=np.uint8).reshape((FULL_FRAME_SIZE[1], FULL_FRAME_SIZE[0], 3)) |
||||||
|
imgff = imgff[:, :, ::-1] # Convert BGR to RGB |
||||||
|
cv2.warpAffine(imgff, np.dot(img_transform, _BB_TO_FULL_FRAME)[:2], |
||||||
|
(img.shape[1], img.shape[0]), dst=img, flags=cv2.WARP_INVERSE_MAP) |
||||||
|
|
||||||
|
intrinsic_matrix = eon_intrinsics |
||||||
|
else: |
||||||
|
img.fill(0) |
||||||
|
intrinsic_matrix = np.eye(3) |
||||||
|
|
||||||
|
if calibration is not None: |
||||||
|
transform = np.dot(img_transform, calibration.model_to_full_frame) |
||||||
|
imgw = cv2.warpAffine(imgff, transform[:2], (MODEL_INPUT_SIZE[0], MODEL_INPUT_SIZE[1]), flags=cv2.WARP_INVERSE_MAP) |
||||||
|
else: |
||||||
|
imgw.fill(0) |
||||||
|
|
||||||
|
sm.update() |
||||||
|
|
||||||
|
w = sm['controlsState'].lateralControlState.which() |
||||||
|
if w == 'lqrState': |
||||||
|
angle_steers_k = sm['controlsState'].lateralControlState.lqrState.steerAngle |
||||||
|
elif w == 'indiState': |
||||||
|
angle_steers_k = sm['controlsState'].lateralControlState.indiState.steerAngle |
||||||
|
else: |
||||||
|
angle_steers_k = np.inf |
||||||
|
|
||||||
|
plot_arr[:-1] = plot_arr[1:] |
||||||
|
plot_arr[-1, name_to_arr_idx['angle_steers']] = sm['controlsState'].angleSteers |
||||||
|
plot_arr[-1, name_to_arr_idx['angle_steers_des']] = sm['carControl'].actuators.steerAngle |
||||||
|
plot_arr[-1, name_to_arr_idx['angle_steers_k']] = angle_steers_k |
||||||
|
plot_arr[-1, name_to_arr_idx['gas']] = sm['carState'].gas |
||||||
|
plot_arr[-1, name_to_arr_idx['computer_gas']] = sm['carControl'].actuators.gas |
||||||
|
plot_arr[-1, name_to_arr_idx['user_brake']] = sm['carState'].brake |
||||||
|
plot_arr[-1, name_to_arr_idx['steer_torque']] = sm['carControl'].actuators.steer * ANGLE_SCALE |
||||||
|
plot_arr[-1, name_to_arr_idx['computer_brake']] = sm['carControl'].actuators.brake |
||||||
|
plot_arr[-1, name_to_arr_idx['v_ego']] = sm['controlsState'].vEgo |
||||||
|
plot_arr[-1, name_to_arr_idx['v_pid']] = sm['controlsState'].vPid |
||||||
|
plot_arr[-1, name_to_arr_idx['v_override']] = sm['carControl'].cruiseControl.speedOverride |
||||||
|
plot_arr[-1, name_to_arr_idx['v_cruise']] = sm['carState'].cruiseState.speed |
||||||
|
plot_arr[-1, name_to_arr_idx['a_ego']] = sm['carState'].aEgo |
||||||
|
plot_arr[-1, name_to_arr_idx['a_target']] = sm['plan'].aTarget |
||||||
|
plot_arr[-1, name_to_arr_idx['accel_override']] = sm['carControl'].cruiseControl.accelOverride |
||||||
|
|
||||||
|
# ***** model **** |
||||||
|
if len(sm['model'].path.poly) > 0: |
||||||
|
model_data = extract_model_data(sm['model']) |
||||||
|
plot_model(model_data, VM, sm['controlsState'].vEgo, sm['controlsState'].curvature, imgw, calibration, |
||||||
|
top_down, np.array(sm['pathPlan'].dPoly)) |
||||||
|
|
||||||
|
# MPC |
||||||
|
if sm.updated['liveMpc']: |
||||||
|
draw_mpc(sm['liveMpc'], top_down) |
||||||
|
|
||||||
|
# draw all radar points |
||||||
|
maybe_update_radar_points(sm['liveTracks'], top_down[1]) |
||||||
|
|
||||||
|
|
||||||
|
if sm.updated['liveCalibration']: |
||||||
|
extrinsic_matrix = np.asarray(sm['liveCalibration'].extrinsicMatrix).reshape(3, 4) |
||||||
|
ke = intrinsic_matrix.dot(extrinsic_matrix) |
||||||
|
warp_matrix = get_camera_frame_from_model_frame(ke) |
||||||
|
calibration = CalibrationTransformsForWarpMatrix(warp_matrix, intrinsic_matrix, extrinsic_matrix) |
||||||
|
|
||||||
|
# draw red pt for lead car in the main img |
||||||
|
for lead in [sm['radarState'].leadOne, sm['radarState'].leadTwo]: |
||||||
|
if lead.status: |
||||||
|
if calibration is not None: |
||||||
|
draw_lead_on(img, lead.dRel, lead.yRel, calibration, color=(192,0,0)) |
||||||
|
|
||||||
|
draw_lead_car(lead.dRel, top_down) |
||||||
|
|
||||||
|
# *** blits *** |
||||||
|
pygame.surfarray.blit_array(camera_surface, img.swapaxes(0,1)) |
||||||
|
screen.blit(camera_surface, (0, 0)) |
||||||
|
|
||||||
|
# display alerts |
||||||
|
alert_line1 = alert1_font.render(sm['controlsState'].alertText1, True, (255,0,0)) |
||||||
|
alert_line2 = alert2_font.render(sm['controlsState'].alertText2, True, (255,0,0)) |
||||||
|
screen.blit(alert_line1, (180, 150)) |
||||||
|
screen.blit(alert_line2, (180, 190)) |
||||||
|
|
||||||
|
if calibration is not None and img is not None: |
||||||
|
cpw = warp_points(CalP, calibration.model_to_bb) |
||||||
|
vanishing_pointw = warp_points(vanishing_point, calibration.model_to_bb) |
||||||
|
pygame.draw.polygon(screen, BLUE, tuple(map(tuple, cpw)), 1) |
||||||
|
pygame.draw.circle(screen, BLUE, list(map(int, map(round, vanishing_pointw[0]))), 2) |
||||||
|
|
||||||
|
if HOR: |
||||||
|
screen.blit(draw_plots(plot_arr), (640+384, 0)) |
||||||
|
else: |
||||||
|
screen.blit(draw_plots(plot_arr), (0, 600)) |
||||||
|
|
||||||
|
pygame.surfarray.blit_array(cameraw_surface, imgw.swapaxes(0, 1)) |
||||||
|
screen.blit(cameraw_surface, (320, 480)) |
||||||
|
|
||||||
|
pygame.surfarray.blit_array(*top_down) |
||||||
|
screen.blit(top_down[0], (640,0)) |
||||||
|
|
||||||
|
i = 0 |
||||||
|
SPACING = 25 |
||||||
|
|
||||||
|
lines = [ |
||||||
|
info_font.render("ENABLED", True, GREEN if sm['controlsState'].enabled else BLACK), |
||||||
|
info_font.render("BRAKE LIGHTS", True, RED if sm['carState'].brakeLights else BLACK), |
||||||
|
info_font.render("SPEED: " + str(round(sm['carState'].vEgo, 1)) + " m/s", True, YELLOW), |
||||||
|
info_font.render("LONG CONTROL STATE: " + str(sm['controlsState'].longControlState), True, YELLOW), |
||||||
|
info_font.render("LONG MPC SOURCE: " + str(sm['plan'].longitudinalPlanSource), True, YELLOW), |
||||||
|
None, |
||||||
|
info_font.render("ANGLE OFFSET (AVG): " + str(round(sm['liveParameters'].angleOffsetAverage, 2)) + " deg", True, YELLOW), |
||||||
|
info_font.render("ANGLE OFFSET (INSTANT): " + str(round(sm['liveParameters'].angleOffset, 2)) + " deg", True, YELLOW), |
||||||
|
info_font.render("STIFFNESS: " + str(round(sm['liveParameters'].stiffnessFactor * 100., 2)) + " %", True, YELLOW), |
||||||
|
info_font.render("STEER RATIO: " + str(round(sm['liveParameters'].steerRatio, 2)), True, YELLOW) |
||||||
|
] |
||||||
|
|
||||||
|
for i, line in enumerate(lines): |
||||||
|
if line is not None: |
||||||
|
screen.blit(line, (write_x, write_y + i * SPACING)) |
||||||
|
|
||||||
|
# this takes time...vsync or something |
||||||
|
pygame.display.flip() |
||||||
|
|
||||||
|
def get_arg_parser(): |
||||||
|
parser = argparse.ArgumentParser( |
||||||
|
description="Show replay data in a UI.", |
||||||
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter) |
||||||
|
|
||||||
|
parser.add_argument("ip_address", nargs="?", default="127.0.0.1", |
||||||
|
help="The ip address on which to receive zmq messages.") |
||||||
|
|
||||||
|
parser.add_argument("--frame-address", default=None, |
||||||
|
help="The frame address (fully qualified ZMQ endpoint for frames) on which to receive zmq messages.") |
||||||
|
return parser |
||||||
|
|
||||||
|
if __name__ == "__main__": |
||||||
|
args = get_arg_parser().parse_args(sys.argv[1:]) |
||||||
|
|
||||||
|
if args.ip_address != "127.0.0.1": |
||||||
|
os.environ["ZMQ"] = "1" |
||||||
|
messaging.context = messaging.Context() |
||||||
|
|
||||||
|
ui_thread(args.ip_address, args.frame_address) |
@ -0,0 +1,446 @@ |
|||||||
|
#!/usr/bin/env python |
||||||
|
import argparse |
||||||
|
import os |
||||||
|
import sys |
||||||
|
import zmq |
||||||
|
import time |
||||||
|
import gc |
||||||
|
import signal |
||||||
|
from threading import Thread |
||||||
|
import numpy as np |
||||||
|
from uuid import uuid4 |
||||||
|
from collections import namedtuple |
||||||
|
from collections import deque |
||||||
|
from multiprocessing import Process, TimeoutError |
||||||
|
from datetime import datetime |
||||||
|
|
||||||
|
# strat 1: script to copy files |
||||||
|
# strat 2: build pip packages around these |
||||||
|
# could be its own pip package, which we'd need to build and release |
||||||
|
from cereal import log as capnp_log |
||||||
|
from cereal.services import service_list |
||||||
|
from cereal.messaging import pub_sock, MultiplePublishersError |
||||||
|
from common import realtime |
||||||
|
|
||||||
|
from tools.lib.file_helpers import mkdirs_exists_ok |
||||||
|
from tools.lib.kbhit import KBHit |
||||||
|
from tools.lib.logreader import MultiLogIterator |
||||||
|
from tools.lib.route import Route |
||||||
|
from tools.lib.route_framereader import RouteFrameReader |
||||||
|
|
||||||
|
# Commands. |
||||||
|
SetRoute = namedtuple("SetRoute", ("name", "start_time", "data_dir")) |
||||||
|
SeekAbsoluteTime = namedtuple("SeekAbsoluteTime", ("secs",)) |
||||||
|
SeekRelativeTime = namedtuple("SeekRelativeTime", ("secs",)) |
||||||
|
TogglePause = namedtuple("TogglePause", ()) |
||||||
|
StopAndQuit = namedtuple("StopAndQuit", ()) |
||||||
|
|
||||||
|
|
||||||
|
class UnloggerWorker(object): |
||||||
|
def __init__(self): |
||||||
|
self._frame_reader = None |
||||||
|
self._cookie = None |
||||||
|
self._readahead = deque() |
||||||
|
|
||||||
|
def run(self, commands_address, data_address, pub_types): |
||||||
|
zmq.Context._instance = None |
||||||
|
commands_socket = zmq.Context.instance().socket(zmq.PULL) |
||||||
|
commands_socket.connect(commands_address) |
||||||
|
|
||||||
|
data_socket = zmq.Context.instance().socket(zmq.PUSH) |
||||||
|
data_socket.connect(data_address) |
||||||
|
|
||||||
|
poller = zmq.Poller() |
||||||
|
poller.register(commands_socket, zmq.POLLIN) |
||||||
|
|
||||||
|
# We can't publish frames without encodeIdx, so add when it's missing. |
||||||
|
if "frame" in pub_types: |
||||||
|
pub_types["encodeIdx"] = None |
||||||
|
|
||||||
|
# gc.set_debug(gc.DEBUG_LEAK | gc.DEBUG_OBJECTS | gc.DEBUG_STATS | gc.DEBUG_SAVEALL | |
||||||
|
# gc.DEBUG_UNCOLLECTABLE) |
||||||
|
|
||||||
|
# TODO: WARNING pycapnp leaks memory all over the place after unlogger runs for a while, gc |
||||||
|
# pauses become huge because there are so many tracked objects solution will be to switch to new |
||||||
|
# cython capnp |
||||||
|
try: |
||||||
|
route = None |
||||||
|
while True: |
||||||
|
while poller.poll(0.) or route is None: |
||||||
|
cookie, cmd = commands_socket.recv_pyobj() |
||||||
|
route = self._process_commands(cmd, route) |
||||||
|
|
||||||
|
# **** get message **** |
||||||
|
self._read_logs(cookie, pub_types) |
||||||
|
self._send_logs(data_socket) |
||||||
|
finally: |
||||||
|
if self._frame_reader is not None: |
||||||
|
self._frame_reader.close() |
||||||
|
data_socket.close() |
||||||
|
commands_socket.close() |
||||||
|
|
||||||
|
def _read_logs(self, cookie, pub_types): |
||||||
|
fullHEVC = capnp_log.EncodeIndex.Type.fullHEVC |
||||||
|
lr = self._lr |
||||||
|
while len(self._readahead) < 1000: |
||||||
|
route_time = lr.tell() |
||||||
|
msg = next(lr) |
||||||
|
typ = msg.which() |
||||||
|
if typ not in pub_types: |
||||||
|
continue |
||||||
|
|
||||||
|
# **** special case certain message types **** |
||||||
|
if typ == "encodeIdx" and msg.encodeIdx.type == fullHEVC: |
||||||
|
# this assumes the encodeIdx always comes before the frame |
||||||
|
self._frame_id_lookup[ |
||||||
|
msg.encodeIdx.frameId] = msg.encodeIdx.segmentNum, msg.encodeIdx.segmentId |
||||||
|
#print "encode", msg.encodeIdx.frameId, len(self._readahead), route_time |
||||||
|
self._readahead.appendleft((typ, msg, route_time, cookie)) |
||||||
|
|
||||||
|
def _send_logs(self, data_socket): |
||||||
|
while len(self._readahead) > 500: |
||||||
|
typ, msg, route_time, cookie = self._readahead.pop() |
||||||
|
smsg = msg.as_builder() |
||||||
|
|
||||||
|
if typ == "frame": |
||||||
|
frame_id = msg.frame.frameId |
||||||
|
|
||||||
|
# Frame exists, make sure we have a framereader. |
||||||
|
# load the frame readers as needed |
||||||
|
s1 = time.time() |
||||||
|
img = self._frame_reader.get(frame_id, pix_fmt="rgb24") |
||||||
|
fr_time = time.time() - s1 |
||||||
|
if fr_time > 0.05: |
||||||
|
print("FRAME(%d) LAG -- %.2f ms" % (frame_id, fr_time*1000.0)) |
||||||
|
|
||||||
|
if img is not None: |
||||||
|
img = img[:, :, ::-1] # Convert RGB to BGR, which is what the camera outputs |
||||||
|
img = img.flatten() |
||||||
|
smsg.frame.image = img.tobytes() |
||||||
|
|
||||||
|
data_socket.send_pyobj((cookie, typ, msg.logMonoTime, route_time), flags=zmq.SNDMORE) |
||||||
|
data_socket.send(smsg.to_bytes(), copy=False) |
||||||
|
|
||||||
|
def _process_commands(self, cmd, route): |
||||||
|
seek_to = None |
||||||
|
if route is None or (isinstance(cmd, SetRoute) and route.name != cmd.name): |
||||||
|
seek_to = cmd.start_time |
||||||
|
route = Route(cmd.name, cmd.data_dir) |
||||||
|
self._lr = MultiLogIterator(route.log_paths(), wraparound=True) |
||||||
|
if self._frame_reader is not None: |
||||||
|
self._frame_reader.close() |
||||||
|
# reset frames for a route |
||||||
|
self._frame_id_lookup = {} |
||||||
|
self._frame_reader = RouteFrameReader( |
||||||
|
route.camera_paths(), None, self._frame_id_lookup, readahead=True) |
||||||
|
|
||||||
|
# always reset this on a seek |
||||||
|
if isinstance(cmd, SeekRelativeTime): |
||||||
|
seek_to = self._lr.tell() + cmd.secs |
||||||
|
elif isinstance(cmd, SeekAbsoluteTime): |
||||||
|
seek_to = cmd.secs |
||||||
|
elif isinstance(cmd, StopAndQuit): |
||||||
|
exit() |
||||||
|
|
||||||
|
if seek_to is not None: |
||||||
|
print("seeking", seek_to) |
||||||
|
if not self._lr.seek(seek_to): |
||||||
|
print("Can't seek: time out of bounds") |
||||||
|
else: |
||||||
|
next(self._lr) # ignore one |
||||||
|
return route |
||||||
|
|
||||||
|
def _get_address_send_func(address): |
||||||
|
sock = pub_sock(address) |
||||||
|
return sock.send |
||||||
|
|
||||||
|
|
||||||
|
def unlogger_thread(command_address, forward_commands_address, data_address, run_realtime, |
||||||
|
address_mapping, publish_time_length, bind_early, no_loop): |
||||||
|
# Clear context to avoid problems with multiprocessing. |
||||||
|
zmq.Context._instance = None |
||||||
|
context = zmq.Context.instance() |
||||||
|
|
||||||
|
command_sock = context.socket(zmq.PULL) |
||||||
|
command_sock.bind(command_address) |
||||||
|
|
||||||
|
forward_commands_socket = context.socket(zmq.PUSH) |
||||||
|
forward_commands_socket.bind(forward_commands_address) |
||||||
|
|
||||||
|
data_socket = context.socket(zmq.PULL) |
||||||
|
data_socket.bind(data_address) |
||||||
|
|
||||||
|
# Set readahead to a reasonable number. |
||||||
|
data_socket.setsockopt(zmq.RCVHWM, 10000) |
||||||
|
|
||||||
|
poller = zmq.Poller() |
||||||
|
poller.register(command_sock, zmq.POLLIN) |
||||||
|
poller.register(data_socket, zmq.POLLIN) |
||||||
|
|
||||||
|
if bind_early: |
||||||
|
send_funcs = { |
||||||
|
typ: _get_address_send_func(address) |
||||||
|
for typ, address in address_mapping.items() |
||||||
|
} |
||||||
|
|
||||||
|
# Give subscribers a chance to connect. |
||||||
|
time.sleep(0.1) |
||||||
|
else: |
||||||
|
send_funcs = {} |
||||||
|
|
||||||
|
start_time = float("inf") |
||||||
|
printed_at = 0 |
||||||
|
generation = 0 |
||||||
|
paused = False |
||||||
|
reset_time = True |
||||||
|
prev_msg_time = None |
||||||
|
while True: |
||||||
|
evts = dict(poller.poll()) |
||||||
|
if command_sock in evts: |
||||||
|
cmd = command_sock.recv_pyobj() |
||||||
|
if isinstance(cmd, TogglePause): |
||||||
|
paused = not paused |
||||||
|
if paused: |
||||||
|
poller.modify(data_socket, 0) |
||||||
|
else: |
||||||
|
poller.modify(data_socket, zmq.POLLIN) |
||||||
|
else: |
||||||
|
# Forward the command the the log data thread. |
||||||
|
# TODO: Remove everything on data_socket. |
||||||
|
generation += 1 |
||||||
|
forward_commands_socket.send_pyobj((generation, cmd)) |
||||||
|
if isinstance(cmd, StopAndQuit): |
||||||
|
return |
||||||
|
|
||||||
|
reset_time = True |
||||||
|
elif data_socket in evts: |
||||||
|
msg_generation, typ, msg_time, route_time = data_socket.recv_pyobj(flags=zmq.RCVMORE) |
||||||
|
msg_bytes = data_socket.recv() |
||||||
|
if msg_generation < generation: |
||||||
|
# Skip packets. |
||||||
|
continue |
||||||
|
|
||||||
|
if no_loop and prev_msg_time is not None and prev_msg_time > msg_time + 1e9: |
||||||
|
generation += 1 |
||||||
|
forward_commands_socket.send_pyobj((generation, StopAndQuit())) |
||||||
|
return |
||||||
|
prev_msg_time = msg_time |
||||||
|
|
||||||
|
msg_time_seconds = msg_time * 1e-9 |
||||||
|
if reset_time: |
||||||
|
msg_start_time = msg_time_seconds |
||||||
|
real_start_time = realtime.sec_since_boot() |
||||||
|
start_time = min(start_time, msg_start_time) |
||||||
|
reset_time = False |
||||||
|
|
||||||
|
if publish_time_length and msg_time_seconds - start_time > publish_time_length: |
||||||
|
generation += 1 |
||||||
|
forward_commands_socket.send_pyobj((generation, StopAndQuit())) |
||||||
|
return |
||||||
|
|
||||||
|
# Print time. |
||||||
|
if abs(printed_at - route_time) > 5.: |
||||||
|
print("at", route_time) |
||||||
|
printed_at = route_time |
||||||
|
|
||||||
|
if typ not in send_funcs: |
||||||
|
if typ in address_mapping: |
||||||
|
# Remove so we don't keep printing warnings. |
||||||
|
address = address_mapping.pop(typ) |
||||||
|
try: |
||||||
|
print("binding", typ) |
||||||
|
send_funcs[typ] = _get_address_send_func(address) |
||||||
|
except Exception as e: |
||||||
|
print("couldn't replay {}: {}".format(typ, e)) |
||||||
|
continue |
||||||
|
else: |
||||||
|
# Skip messages that we are not registered to publish. |
||||||
|
continue |
||||||
|
|
||||||
|
# Sleep as needed for real time playback. |
||||||
|
if run_realtime: |
||||||
|
msg_time_offset = msg_time_seconds - msg_start_time |
||||||
|
real_time_offset = realtime.sec_since_boot() - real_start_time |
||||||
|
lag = msg_time_offset - real_time_offset |
||||||
|
if lag > 0 and lag < 30: # a large jump is OK, likely due to an out of order segment |
||||||
|
if lag > 1: |
||||||
|
print("sleeping for", lag) |
||||||
|
time.sleep(lag) |
||||||
|
elif lag < -1: |
||||||
|
# Relax the real time schedule when we slip far behind. |
||||||
|
reset_time = True |
||||||
|
|
||||||
|
# Send message. |
||||||
|
try: |
||||||
|
send_funcs[typ](msg_bytes) |
||||||
|
except MultiplePublishersError: |
||||||
|
del send_funcs[typ] |
||||||
|
|
||||||
|
def timestamp_to_s(tss): |
||||||
|
return time.mktime(datetime.strptime(tss, '%Y-%m-%d--%H-%M-%S').timetuple()) |
||||||
|
|
||||||
|
def absolute_time_str(s, start_time): |
||||||
|
try: |
||||||
|
# first try if it's a float |
||||||
|
return float(s) |
||||||
|
except ValueError: |
||||||
|
# now see if it's a timestamp |
||||||
|
return timestamp_to_s(s) - start_time |
||||||
|
|
||||||
|
def _get_address_mapping(args): |
||||||
|
if args.min is not None: |
||||||
|
services_to_mock = [ |
||||||
|
'thermal', 'can', 'health', 'sensorEvents', 'gpsNMEA', 'frame', 'encodeIdx', |
||||||
|
'model', 'features', 'liveLocation', 'gpsLocation' |
||||||
|
] |
||||||
|
elif args.enabled is not None: |
||||||
|
services_to_mock = args.enabled |
||||||
|
else: |
||||||
|
services_to_mock = service_list.keys() |
||||||
|
|
||||||
|
address_mapping = {service_name: service_name for service_name in services_to_mock} |
||||||
|
address_mapping.update(dict(args.address_mapping)) |
||||||
|
|
||||||
|
for k in args.disabled: |
||||||
|
address_mapping.pop(k, None) |
||||||
|
|
||||||
|
non_services = set(address_mapping) - set(service_list) |
||||||
|
if non_services: |
||||||
|
print("WARNING: Unknown services {}".format(list(non_services))) |
||||||
|
|
||||||
|
return address_mapping |
||||||
|
|
||||||
|
def keyboard_controller_thread(q, route_start_time): |
||||||
|
print("keyboard waiting for input") |
||||||
|
kb = KBHit() |
||||||
|
while 1: |
||||||
|
c = kb.getch() |
||||||
|
if c=='m': # Move forward by 1m |
||||||
|
q.send_pyobj(SeekRelativeTime(60)) |
||||||
|
elif c=='M': # Move backward by 1m |
||||||
|
q.send_pyobj(SeekRelativeTime(-60)) |
||||||
|
elif c=='s': # Move forward by 10s |
||||||
|
q.send_pyobj(SeekRelativeTime(10)) |
||||||
|
elif c=='S': # Move backward by 10s |
||||||
|
q.send_pyobj(SeekRelativeTime(-10)) |
||||||
|
elif c=='G': # Move backward by 10s |
||||||
|
q.send_pyobj(SeekAbsoluteTime(0.)) |
||||||
|
elif c=="\x20": # Space bar. |
||||||
|
q.send_pyobj(TogglePause()) |
||||||
|
elif c=="\n": |
||||||
|
try: |
||||||
|
seek_time_input = raw_input('time: ') |
||||||
|
seek_time = absolute_time_str(seek_time_input, route_start_time) |
||||||
|
|
||||||
|
q.send_pyobj(SeekAbsoluteTime(seek_time)) |
||||||
|
except Exception as e: |
||||||
|
print("Time not understood: {}".format(e)) |
||||||
|
|
||||||
|
def get_arg_parser(): |
||||||
|
parser = argparse.ArgumentParser( |
||||||
|
description="Mock openpilot components by publishing logged messages.", |
||||||
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter) |
||||||
|
|
||||||
|
parser.add_argument("route_name", type=(lambda x: x.replace("#", "|")), nargs="?", |
||||||
|
help="The route whose messages will be published.") |
||||||
|
parser.add_argument("data_dir", nargs='?', default=os.getenv('UNLOGGER_DATA_DIR'), |
||||||
|
help="Path to directory in which log and camera files are located.") |
||||||
|
|
||||||
|
parser.add_argument("--no-loop", action="store_true", help="Stop at the end of the replay.") |
||||||
|
|
||||||
|
key_value_pair = lambda x: x.split("=") |
||||||
|
parser.add_argument("address_mapping", nargs="*", type=key_value_pair, |
||||||
|
help="Pairs <service>=<zmq_addr> to publish <service> on <zmq_addr>.") |
||||||
|
|
||||||
|
comma_list = lambda x: x.split(",") |
||||||
|
to_mock_group = parser.add_mutually_exclusive_group() |
||||||
|
to_mock_group.add_argument("--min", action="store_true", default=os.getenv("MIN")) |
||||||
|
to_mock_group.add_argument("--enabled", default=os.getenv("ENABLED"), type=comma_list) |
||||||
|
|
||||||
|
parser.add_argument("--disabled", type=comma_list, default=os.getenv("DISABLED") or ()) |
||||||
|
|
||||||
|
parser.add_argument( |
||||||
|
"--tl", dest="publish_time_length", type=float, default=None, |
||||||
|
help="Length of interval in event time for which messages should be published.") |
||||||
|
|
||||||
|
parser.add_argument( |
||||||
|
"--no-realtime", dest="realtime", action="store_false", default=True, |
||||||
|
help="Publish messages as quickly as possible instead of realtime.") |
||||||
|
|
||||||
|
parser.add_argument( |
||||||
|
"--no-interactive", dest="interactive", action="store_false", default=True, |
||||||
|
help="Disable interactivity.") |
||||||
|
|
||||||
|
parser.add_argument( |
||||||
|
"--bind-early", action="store_true", default=False, |
||||||
|
help="Bind early to avoid dropping messages.") |
||||||
|
|
||||||
|
return parser |
||||||
|
|
||||||
|
def main(argv): |
||||||
|
args = get_arg_parser().parse_args(sys.argv[1:]) |
||||||
|
|
||||||
|
command_address = "ipc:///tmp/{}".format(uuid4()) |
||||||
|
forward_commands_address = "ipc:///tmp/{}".format(uuid4()) |
||||||
|
data_address = "ipc:///tmp/{}".format(uuid4()) |
||||||
|
|
||||||
|
address_mapping = _get_address_mapping(args) |
||||||
|
|
||||||
|
command_sock = zmq.Context.instance().socket(zmq.PUSH) |
||||||
|
command_sock.connect(command_address) |
||||||
|
|
||||||
|
if args.route_name is not None: |
||||||
|
route_name_split = args.route_name.split("|") |
||||||
|
if len(route_name_split) > 1: |
||||||
|
route_start_time = timestamp_to_s(route_name_split[1]) |
||||||
|
else: |
||||||
|
route_start_time = 0 |
||||||
|
command_sock.send_pyobj( |
||||||
|
SetRoute(args.route_name, 0, args.data_dir)) |
||||||
|
else: |
||||||
|
print("waiting for external command...") |
||||||
|
route_start_time = 0 |
||||||
|
|
||||||
|
subprocesses = {} |
||||||
|
try: |
||||||
|
subprocesses["data"] = Process( |
||||||
|
target=UnloggerWorker().run, |
||||||
|
args=(forward_commands_address, data_address, address_mapping.copy())) |
||||||
|
|
||||||
|
subprocesses["control"] = Process( |
||||||
|
target=unlogger_thread, |
||||||
|
args=(command_address, forward_commands_address, data_address, args.realtime, |
||||||
|
_get_address_mapping(args), args.publish_time_length, args.bind_early, args.no_loop)) |
||||||
|
|
||||||
|
for p in subprocesses.values(): |
||||||
|
p.daemon = True |
||||||
|
|
||||||
|
subprocesses["data"].start() |
||||||
|
subprocesses["control"].start() |
||||||
|
|
||||||
|
# Exit if any of the children die. |
||||||
|
def exit_if_children_dead(*_): |
||||||
|
for name, p in subprocesses.items(): |
||||||
|
if not p.is_alive(): |
||||||
|
[p.terminate() for p in subprocesses.values()] |
||||||
|
exit() |
||||||
|
signal.signal(signal.SIGCHLD, signal.SIGIGN) |
||||||
|
signal.signal(signal.SIGCHLD, exit_if_children_dead) |
||||||
|
|
||||||
|
if args.interactive: |
||||||
|
keyboard_controller_thread(command_sock, route_start_time) |
||||||
|
else: |
||||||
|
# Wait forever for children. |
||||||
|
while True: |
||||||
|
time.sleep(10000.) |
||||||
|
finally: |
||||||
|
for p in subprocesses.values(): |
||||||
|
if p.is_alive(): |
||||||
|
try: |
||||||
|
p.join(3.) |
||||||
|
except TimeoutError: |
||||||
|
p.terminate() |
||||||
|
continue |
||||||
|
|
||||||
|
if __name__ == "__main__": |
||||||
|
sys.exit(main(sys.argv[1:])) |
@ -0,0 +1,11 @@ |
|||||||
|
aenum |
||||||
|
atomicwrites |
||||||
|
futures |
||||||
|
libarchive |
||||||
|
lru-dict |
||||||
|
matplotlib==2.0.2 |
||||||
|
numpy |
||||||
|
opencv-python |
||||||
|
pygame |
||||||
|
hexdump==3.3 |
||||||
|
av==0.5.0 |
@ -0,0 +1,3 @@ |
|||||||
|
CARLA_*.tar.gz |
||||||
|
carla |
||||||
|
|
@ -0,0 +1,100 @@ |
|||||||
|
#!/usr/bin/env python3 |
||||||
|
import time |
||||||
|
import cereal.messaging as messaging |
||||||
|
from opendbc.can.parser import CANParser |
||||||
|
from opendbc.can.packer import CANPacker |
||||||
|
from selfdrive.boardd.boardd_api_impl import can_list_to_can_capnp |
||||||
|
from selfdrive.car.honda.values import FINGERPRINTS, CAR |
||||||
|
from selfdrive.car import crc8_pedal |
||||||
|
|
||||||
|
from selfdrive.test.longitudinal_maneuvers.plant import get_car_can_parser |
||||||
|
cp = get_car_can_parser() |
||||||
|
#cp = CANParser("honda_civic_touring_2016_can_generated") |
||||||
|
|
||||||
|
packer = CANPacker("honda_civic_touring_2016_can_generated") |
||||||
|
rpacker = CANPacker("acura_ilx_2016_nidec") |
||||||
|
|
||||||
|
def can_function(pm, speed, angle, idx, engage): |
||||||
|
msg = [] |
||||||
|
msg.append(packer.make_can_msg("ENGINE_DATA", 0, {"XMISSION_SPEED": speed}, idx)) |
||||||
|
msg.append(packer.make_can_msg("WHEEL_SPEEDS", 0, |
||||||
|
{"WHEEL_SPEED_FL": speed, |
||||||
|
"WHEEL_SPEED_FR": speed, |
||||||
|
"WHEEL_SPEED_RL": speed, |
||||||
|
"WHEEL_SPEED_RR": speed}, -1)) |
||||||
|
|
||||||
|
if engage: |
||||||
|
msg.append(packer.make_can_msg("SCM_BUTTONS", 0, {"CRUISE_BUTTONS": 3}, idx)) |
||||||
|
else: |
||||||
|
msg.append(packer.make_can_msg("SCM_BUTTONS", 0, {"CRUISE_BUTTONS": 0}, idx)) |
||||||
|
|
||||||
|
values = {"COUNTER_PEDAL": idx&0xF} |
||||||
|
checksum = crc8_pedal(packer.make_can_msg("GAS_SENSOR", 0, {"COUNTER_PEDAL": idx&0xF}, -1)[2][:-1]) |
||||||
|
values["CHECKSUM_PEDAL"] = checksum |
||||||
|
msg.append(packer.make_can_msg("GAS_SENSOR", 0, values, -1)) |
||||||
|
|
||||||
|
msg.append(packer.make_can_msg("GEARBOX", 0, {"GEAR": 4, "GEAR_SHIFTER": 8}, idx)) |
||||||
|
msg.append(packer.make_can_msg("GAS_PEDAL_2", 0, {}, idx)) |
||||||
|
msg.append(packer.make_can_msg("SEATBELT_STATUS", 0, {"SEATBELT_DRIVER_LATCHED": 1}, idx)) |
||||||
|
msg.append(packer.make_can_msg("STEER_STATUS", 0, {}, idx)) |
||||||
|
msg.append(packer.make_can_msg("STEERING_SENSORS", 0, {"STEER_ANGLE": angle}, idx)) |
||||||
|
msg.append(packer.make_can_msg("POWERTRAIN_DATA", 0, {}, idx)) |
||||||
|
msg.append(packer.make_can_msg("VSA_STATUS", 0, {}, idx)) |
||||||
|
msg.append(packer.make_can_msg("STANDSTILL", 0, {}, idx)) |
||||||
|
msg.append(packer.make_can_msg("STEER_MOTOR_TORQUE", 0, {}, idx)) |
||||||
|
msg.append(packer.make_can_msg("EPB_STATUS", 0, {}, idx)) |
||||||
|
msg.append(packer.make_can_msg("DOORS_STATUS", 0, {}, idx)) |
||||||
|
msg.append(packer.make_can_msg("CRUISE_PARAMS", 0, {}, idx)) |
||||||
|
msg.append(packer.make_can_msg("CRUISE", 0, {}, idx)) |
||||||
|
msg.append(packer.make_can_msg("SCM_FEEDBACK", 0, {"MAIN_ON": 1}, idx)) |
||||||
|
#print(msg) |
||||||
|
|
||||||
|
# cam bus |
||||||
|
msg.append(packer.make_can_msg("STEERING_CONTROL", 2, {}, idx)) |
||||||
|
msg.append(packer.make_can_msg("ACC_HUD", 2, {}, idx)) |
||||||
|
msg.append(packer.make_can_msg("BRAKE_COMMAND", 2, {}, idx)) |
||||||
|
|
||||||
|
# radar |
||||||
|
if idx%5 == 0: |
||||||
|
msg.append(rpacker.make_can_msg("RADAR_DIAGNOSTIC", 1, {"RADAR_STATE": 0x79}, -1)) |
||||||
|
for i in range(16): |
||||||
|
msg.append(rpacker.make_can_msg("TRACK_%d" % i, 1, {"LONG_DIST": 255.5}, -1)) |
||||||
|
|
||||||
|
# fill in the rest for fingerprint |
||||||
|
done = set([x[0] for x in msg]) |
||||||
|
for k,v in FINGERPRINTS[CAR.CIVIC][0].items(): |
||||||
|
if k not in done and k not in [0xE4, 0x194]: |
||||||
|
msg.append([k, 0, b'\x00'*v, 0]) |
||||||
|
pm.send('can', can_list_to_can_capnp(msg)) |
||||||
|
|
||||||
|
def sendcan_function(sendcan): |
||||||
|
sc = messaging.drain_sock_raw(sendcan) |
||||||
|
cp.update_strings(sc, sendcan=True) |
||||||
|
|
||||||
|
if cp.vl[0x1fa]['COMPUTER_BRAKE_REQUEST']: |
||||||
|
brake = cp.vl[0x1fa]['COMPUTER_BRAKE'] * 0.003906248 |
||||||
|
else: |
||||||
|
brake = 0.0 |
||||||
|
|
||||||
|
if cp.vl[0x200]['GAS_COMMAND'] > 0: |
||||||
|
gas = cp.vl[0x200]['GAS_COMMAND'] / 256.0 |
||||||
|
else: |
||||||
|
gas = 0.0 |
||||||
|
|
||||||
|
if cp.vl[0xe4]['STEER_TORQUE_REQUEST']: |
||||||
|
steer_torque = cp.vl[0xe4]['STEER_TORQUE']*1.0/0x1000 |
||||||
|
else: |
||||||
|
steer_torque = 0.0 |
||||||
|
|
||||||
|
return (gas, brake, steer_torque) |
||||||
|
|
||||||
|
if __name__ == "__main__": |
||||||
|
pm = messaging.PubMaster(['can']) |
||||||
|
sendcan = messaging.sub_sock('sendcan') |
||||||
|
idx = 0 |
||||||
|
while 1: |
||||||
|
sendcan_function(sendcan) |
||||||
|
can_function(pm, 10.0, idx) |
||||||
|
time.sleep(0.01) |
||||||
|
idx += 1 |
||||||
|
|
@ -0,0 +1,155 @@ |
|||||||
|
#!/usr/bin/env python3 |
||||||
|
import os |
||||||
|
import time |
||||||
|
import math |
||||||
|
import atexit |
||||||
|
import numpy as np |
||||||
|
import threading |
||||||
|
import carla |
||||||
|
import random |
||||||
|
import cereal.messaging as messaging |
||||||
|
from common.params import Params |
||||||
|
from common.realtime import Ratekeeper |
||||||
|
from can import can_function, sendcan_function |
||||||
|
import queue |
||||||
|
|
||||||
|
pm = messaging.PubMaster(['frame', 'sensorEvents', 'can']) |
||||||
|
|
||||||
|
W,H = 1164, 874 |
||||||
|
|
||||||
|
def cam_callback(image): |
||||||
|
img = np.frombuffer(image.raw_data, dtype=np.dtype("uint8")) |
||||||
|
img = np.reshape(img, (H, W, 4)) |
||||||
|
img = img[:, :, [0,1,2]].copy() |
||||||
|
|
||||||
|
dat = messaging.new_message() |
||||||
|
dat.init('frame') |
||||||
|
dat.frame = { |
||||||
|
"frameId": image.frame, |
||||||
|
"image": img.tostring(), |
||||||
|
} |
||||||
|
pm.send('frame', dat) |
||||||
|
|
||||||
|
def imu_callback(imu): |
||||||
|
#print(imu, imu.accelerometer) |
||||||
|
|
||||||
|
dat = messaging.new_message() |
||||||
|
dat.init('sensorEvents', 2) |
||||||
|
dat.sensorEvents[0].sensor = 4 |
||||||
|
dat.sensorEvents[0].type = 0x10 |
||||||
|
dat.sensorEvents[0].init('acceleration') |
||||||
|
dat.sensorEvents[0].acceleration.v = [imu.accelerometer.x, imu.accelerometer.y, imu.accelerometer.z] |
||||||
|
# copied these numbers from locationd |
||||||
|
dat.sensorEvents[1].sensor = 5 |
||||||
|
dat.sensorEvents[1].type = 0x10 |
||||||
|
dat.sensorEvents[1].init('gyroUncalibrated') |
||||||
|
dat.sensorEvents[1].gyroUncalibrated.v = [imu.gyroscope.x, imu.gyroscope.y, imu.gyroscope.z] |
||||||
|
pm.send('sensorEvents', dat) |
||||||
|
|
||||||
|
def health_function(): |
||||||
|
pm = messaging.PubMaster(['health']) |
||||||
|
rk = Ratekeeper(1.0) |
||||||
|
while 1: |
||||||
|
dat = messaging.new_message() |
||||||
|
dat.init('health') |
||||||
|
dat.valid = True |
||||||
|
dat.health = { |
||||||
|
'ignitionLine': True, |
||||||
|
'hwType': "whitePanda", |
||||||
|
'controlsAllowed': True |
||||||
|
} |
||||||
|
pm.send('health', dat) |
||||||
|
rk.keep_time() |
||||||
|
|
||||||
|
def fake_driver_monitoring(): |
||||||
|
pm = messaging.PubMaster(['driverMonitoring']) |
||||||
|
while 1: |
||||||
|
dat = messaging.new_message() |
||||||
|
dat.init('driverMonitoring') |
||||||
|
dat.driverMonitoring.faceProb = 1.0 |
||||||
|
pm.send('driverMonitoring', dat) |
||||||
|
time.sleep(0.1) |
||||||
|
|
||||||
|
def go(): |
||||||
|
client = carla.Client("127.0.0.1", 2000) |
||||||
|
client.set_timeout(5.0) |
||||||
|
world = client.load_world('Town03') |
||||||
|
|
||||||
|
settings = world.get_settings() |
||||||
|
settings.fixed_delta_seconds = 0.05 |
||||||
|
world.apply_settings(settings) |
||||||
|
|
||||||
|
weather = carla.WeatherParameters( |
||||||
|
cloudyness=0.0, |
||||||
|
precipitation=0.0, |
||||||
|
precipitation_deposits=0.0, |
||||||
|
wind_intensity=0.0, |
||||||
|
sun_azimuth_angle=0.0, |
||||||
|
sun_altitude_angle=0.0) |
||||||
|
world.set_weather(weather) |
||||||
|
|
||||||
|
blueprint_library = world.get_blueprint_library() |
||||||
|
""" |
||||||
|
for blueprint in blueprint_library.filter('sensor.*'): |
||||||
|
print(blueprint.id) |
||||||
|
exit(0) |
||||||
|
""" |
||||||
|
|
||||||
|
world_map = world.get_map() |
||||||
|
|
||||||
|
vehicle_bp = random.choice(blueprint_library.filter('vehicle.bmw.*')) |
||||||
|
vehicle = world.spawn_actor(vehicle_bp, random.choice(world_map.get_spawn_points())) |
||||||
|
#vehicle.set_autopilot(True) |
||||||
|
|
||||||
|
blueprint = blueprint_library.find('sensor.camera.rgb') |
||||||
|
blueprint.set_attribute('image_size_x', str(W)) |
||||||
|
blueprint.set_attribute('image_size_y', str(H)) |
||||||
|
blueprint.set_attribute('fov', '70') |
||||||
|
blueprint.set_attribute('sensor_tick', '0.05') |
||||||
|
transform = carla.Transform(carla.Location(x=0.8, z=1.45)) |
||||||
|
camera = world.spawn_actor(blueprint, transform, attach_to=vehicle) |
||||||
|
camera.listen(cam_callback) |
||||||
|
|
||||||
|
# TODO: wait for carla 0.9.7 |
||||||
|
imu_bp = blueprint_library.find('sensor.other.imu') |
||||||
|
imu = world.spawn_actor(imu_bp, transform, attach_to=vehicle) |
||||||
|
imu.listen(imu_callback) |
||||||
|
|
||||||
|
def destroy(): |
||||||
|
print("clean exit") |
||||||
|
imu.destroy() |
||||||
|
camera.destroy() |
||||||
|
vehicle.destroy() |
||||||
|
print("done") |
||||||
|
atexit.register(destroy) |
||||||
|
|
||||||
|
threading.Thread(target=health_function).start() |
||||||
|
threading.Thread(target=fake_driver_monitoring).start() |
||||||
|
|
||||||
|
# can loop |
||||||
|
sendcan = messaging.sub_sock('sendcan') |
||||||
|
rk = Ratekeeper(100) |
||||||
|
steer_angle = 0 |
||||||
|
while 1: |
||||||
|
vel = vehicle.get_velocity() |
||||||
|
speed = math.sqrt(vel.x**2 + vel.y**2 + vel.z**2) |
||||||
|
|
||||||
|
can_function(pm, speed, steer_angle, rk.frame, rk.frame%500 == 499) |
||||||
|
if rk.frame%5 == 0: |
||||||
|
throttle, brake, steer = sendcan_function(sendcan) |
||||||
|
steer_angle += steer/10000.0 # torque |
||||||
|
vc = carla.VehicleControl(throttle=throttle, steer=steer_angle, brake=brake) |
||||||
|
vehicle.apply_control(vc) |
||||||
|
print(speed, steer_angle, vc) |
||||||
|
|
||||||
|
rk.keep_time() |
||||||
|
|
||||||
|
if __name__ == "__main__": |
||||||
|
params = Params() |
||||||
|
params.delete("Offroad_ConnectivityNeeded") |
||||||
|
from selfdrive.version import terms_version, training_version |
||||||
|
params.put("HasAcceptedTerms", terms_version) |
||||||
|
params.put("CompletedTrainingVersion", training_version) |
||||||
|
|
||||||
|
go() |
||||||
|
|
@ -0,0 +1,10 @@ |
|||||||
|
#!/bin/bash -e |
||||||
|
FILE=CARLA_0.9.7.tar.gz |
||||||
|
if [ ! -f $FILE ]; then |
||||||
|
curl -O http://carla-assets-internal.s3.amazonaws.com/Releases/Linux/$FILE |
||||||
|
fi |
||||||
|
mkdir -p carla |
||||||
|
cd carla |
||||||
|
tar xvf ../$FILE |
||||||
|
easy_install PythonAPI/carla/dist/carla-0.9.7-py3.5-linux-x86_64.egg |
||||||
|
|
@ -0,0 +1,5 @@ |
|||||||
|
#!/bin/bash |
||||||
|
cd ~/one/tools/nui |
||||||
|
# vision, boardd, sensorsd, gpsd |
||||||
|
ALLOW=frame,can,ubloxRaw,health,sensorEvents,gpsNMEA,gpsLocation ./nui "02ec6bea180a4d36/2019-10-25--10-18-09" |
||||||
|
|
@ -0,0 +1,4 @@ |
|||||||
|
#!/bin/bash |
||||||
|
cd carla |
||||||
|
./CarlaUE4.sh |
||||||
|
|
@ -0,0 +1,9 @@ |
|||||||
|
Host EON-smays |
||||||
|
HostName 192.168.5.11 |
||||||
|
Port 8022 |
||||||
|
IdentityFile key/id_rsa |
||||||
|
|
||||||
|
Host EON-wifi |
||||||
|
HostName 192.168.43.1 |
||||||
|
Port 8022 |
||||||
|
IdentityFile key/id_rsa |
@ -0,0 +1,28 @@ |
|||||||
|
-----BEGIN PRIVATE KEY----- |
||||||
|
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC+iXXq30Tq+J5N |
||||||
|
Kat3KWHCzcmwZ55nGh6WggAqECa5CasBlM9VeROpVu3beA+5h0MibRgbD4DMtVXB |
||||||
|
t6gEvZ8nd04E7eLA9LTZyFDZ7SkSOVj4oXOQsT0GnJmKrASW5KslTWqVzTfo2XCt |
||||||
|
Z+004ikLxmyFeBO8NOcErW1pa8gFdQDToH9FrA7kgysic/XVESTOoe7XlzRoe/eZ |
||||||
|
acEQ+jtnmFd21A4aEADkk00Ahjr0uKaJiLUAPatxs2icIXWpgYtfqqtaKF23wSt6 |
||||||
|
1OTu6cAwXbOWr3m+IUSRUO0IRzEIQS3z1jfd1svgzSgSSwZ1Lhj4AoKxIEAIc8qJ |
||||||
|
rO4uymCJAgMBAAECggEBAISFevxHGdoL3Z5xkw6oO5SQKO2GxEeVhRzNgmu/HA+q |
||||||
|
x8OryqD6O1CWY4037kft6iWxlwiLOdwna2P25ueVM3LxqdQH2KS4DmlCx+kq6FwC |
||||||
|
gv063fQPMhC9LpWimvaQSPEC7VUPjQlo4tPY6sTTYBUOh0A1ihRm/x7juKuQCWix |
||||||
|
Cq8C/DVnB1X4mGj+W3nJc5TwVJtgJbbiBrq6PWrhvB/3qmkxHRL7dU2SBb2iNRF1 |
||||||
|
LLY30dJx/cD73UDKNHrlrsjk3UJc29Mp4/MladKvUkRqNwlYxSuAtJV0nZ3+iFkL |
||||||
|
s3adSTHdJpClQer45R51rFDlVsDz2ZBpb/hRNRoGDuECgYEA6A1EixLq7QYOh3cb |
||||||
|
Xhyh3W4kpVvA/FPfKH1OMy3ONOD/Y9Oa+M/wthW1wSoRL2n+uuIW5OAhTIvIEivj |
||||||
|
6bAZsTT3twrvOrvYu9rx9aln4p8BhyvdjeW4kS7T8FP5ol6LoOt2sTP3T1LOuJPO |
||||||
|
uQvOjlKPKIMh3c3RFNWTnGzMPa0CgYEA0jNiPLxP3A2nrX0keKDI+VHuvOY88gdh |
||||||
|
0W5BuLMLovOIDk9aQFIbBbMuW1OTjHKv9NK+Lrw+YbCFqOGf1dU/UN5gSyE8lX/Q |
||||||
|
FsUGUqUZx574nJZnOIcy3ONOnQLcvHAQToLFAGUd7PWgP3CtHkt9hEv2koUwL4vo |
||||||
|
ikTP1u9Gkc0CgYEA2apoWxPZrY963XLKBxNQecYxNbLFaWq67t3rFnKm9E8BAICi |
||||||
|
4zUaE5J1tMVi7Vi9iks9Ml9SnNyZRQJKfQ+kaebHXbkyAaPmfv+26rqHKboA0uxA |
||||||
|
nDOZVwXX45zBkp6g1sdHxJx8JLoGEnkC9eyvSi0C//tRLx86OhLErXwYcNkCf1it |
||||||
|
VMRKrWYoXJTUNo6tRhvodM88UnnIo3u3CALjhgU4uC1RTMHV4ZCGBwiAOb8GozSl |
||||||
|
s5YD1E1iKwEULloHnK6BIh6P5v8q7J6uf/xdqoKMjlWBHgq6/roxKvkSPA1DOZ3l |
||||||
|
jTadcgKFnRUmc+JT9p/ZbCxkA/ALFg8++G+0ghECgYA8vG3M/utweLvq4RI7l7U7 |
||||||
|
b+i2BajfK2OmzNi/xugfeLjY6k2tfQGRuv6ppTjehtji2uvgDWkgjJUgPfZpir3I |
||||||
|
RsVMUiFgloWGHETOy0Qvc5AwtqTJFLTD1Wza2uBilSVIEsg6Y83Gickh+ejOmEsY |
||||||
|
6co17RFaAZHwGfCFFjO76Q== |
||||||
|
-----END PRIVATE KEY----- |
@ -0,0 +1,3 @@ |
|||||||
|
# enp5s0 is the smays network name. Change it appropriately if you are using an ethernet adapter (type ifconfig to get the proper network name) |
||||||
|
sudo ifconfig enp5s0 192.168.5.1 netmask 255.255.255.0 |
||||||
|
ssh -F config EON-smays |
@ -0,0 +1 @@ |
|||||||
|
ssh -F config EON-wifi |
After Width: | Height: | Size: 7.0 MiB |
After Width: | Height: | Size: 4.4 MiB |
@ -0,0 +1,93 @@ |
|||||||
|
#!/usr/bin/env python |
||||||
|
import os |
||||||
|
import sys |
||||||
|
import argparse |
||||||
|
import zmq |
||||||
|
import json |
||||||
|
import cv2 |
||||||
|
import numpy as np |
||||||
|
from hexdump import hexdump |
||||||
|
import scipy.misc |
||||||
|
import struct |
||||||
|
from collections import deque |
||||||
|
|
||||||
|
# sudo pip install git+git://github.com/mikeboers/PyAV.git |
||||||
|
import av |
||||||
|
|
||||||
|
import cereal.messaging as messaging |
||||||
|
from cereal.services import service_list |
||||||
|
|
||||||
|
PYGAME = os.getenv("PYGAME") is not None |
||||||
|
if PYGAME: |
||||||
|
import pygame |
||||||
|
imgff = np.zeros((874, 1164, 3), dtype=np.uint8) |
||||||
|
|
||||||
|
# first 74 bytes in any stream |
||||||
|
start = "0000000140010c01ffff016000000300b0000003000003005dac5900000001420101016000000300b0000003000003005da0025080381c5c665aee4c92ec80000000014401c0f1800420" |
||||||
|
|
||||||
|
def receiver_thread(): |
||||||
|
if PYGAME: |
||||||
|
pygame.init() |
||||||
|
pygame.display.set_caption("vnet debug UI") |
||||||
|
screen = pygame.display.set_mode((1164,874), pygame.DOUBLEBUF) |
||||||
|
camera_surface = pygame.surface.Surface((1164,874), 0, 24).convert() |
||||||
|
|
||||||
|
addr = "192.168.5.11" |
||||||
|
if len(sys.argv) >= 2: |
||||||
|
addr = sys.argv[1] |
||||||
|
|
||||||
|
context = zmq.Context() |
||||||
|
s = messaging.sub_sock(context, 9002, addr=addr) |
||||||
|
frame_sock = messaging.pub_sock(context, service_list['frame'].port) |
||||||
|
|
||||||
|
ctx = av.codec.codec.Codec('hevc', 'r').create() |
||||||
|
ctx.decode(av.packet.Packet(start.decode("hex"))) |
||||||
|
|
||||||
|
import time |
||||||
|
while 1: |
||||||
|
t1 = time.time() |
||||||
|
ts, raw = s.recv_multipart() |
||||||
|
ts = struct.unpack('q', ts)[0] * 1000 |
||||||
|
t1, t2 = time.time(), t1 |
||||||
|
#print 'ms to get frame:', (t1-t2)*1000 |
||||||
|
|
||||||
|
pkt = av.packet.Packet(raw) |
||||||
|
f = ctx.decode(pkt) |
||||||
|
if not f: |
||||||
|
continue |
||||||
|
f = f[0] |
||||||
|
t1, t2 = time.time(), t1 |
||||||
|
#print 'ms to decode:', (t1-t2)*1000 |
||||||
|
|
||||||
|
y_plane = np.frombuffer(f.planes[0], np.uint8).reshape((874, 1216))[:, 0:1164] |
||||||
|
u_plane = np.frombuffer(f.planes[1], np.uint8).reshape((437, 608))[:, 0:582] |
||||||
|
v_plane = np.frombuffer(f.planes[2], np.uint8).reshape((437, 608))[:, 0:582] |
||||||
|
yuv_img = y_plane.tobytes() + u_plane.tobytes() + v_plane.tobytes() |
||||||
|
t1, t2 = time.time(), t1 |
||||||
|
#print 'ms to make yuv:', (t1-t2)*1000 |
||||||
|
#print 'tsEof:', ts |
||||||
|
|
||||||
|
dat = messaging.new_message() |
||||||
|
dat.init('frame') |
||||||
|
dat.frame.image = yuv_img |
||||||
|
dat.frame.timestampEof = ts |
||||||
|
dat.frame.transform = map(float, list(np.eye(3).flatten())) |
||||||
|
frame_sock.send(dat.to_bytes()) |
||||||
|
|
||||||
|
if PYGAME: |
||||||
|
yuv_np = np.frombuffer(yuv_img, dtype=np.uint8).reshape(874 * 3 // 2, -1) |
||||||
|
cv2.cvtColor(yuv_np, cv2.COLOR_YUV2RGB_I420, dst=imgff) |
||||||
|
#print yuv_np.shape, imgff.shape |
||||||
|
|
||||||
|
#scipy.misc.imsave("tmp.png", imgff) |
||||||
|
|
||||||
|
pygame.surfarray.blit_array(camera_surface, imgff.swapaxes(0,1)) |
||||||
|
screen.blit(camera_surface, (0, 0)) |
||||||
|
pygame.display.flip() |
||||||
|
|
||||||
|
|
||||||
|
def main(gctx=None): |
||||||
|
receiver_thread() |
||||||
|
|
||||||
|
if __name__ == "__main__": |
||||||
|
main() |
Loading…
Reference in new issue