Add: initial ODROID-N1 Rockchip hardware decoder support

This commit is contained in:
AreaScout 2018-03-05 23:23:19 +00:00
parent d452411428
commit c0cc20733b
7 changed files with 543 additions and 4 deletions

View File

@ -14,6 +14,7 @@ find_package(Opus REQUIRED)
find_package(Broadcom)
find_package(Freescale)
find_package(Amlogic)
find_package(Rockchip)
find_package(PkgConfig REQUIRED)
pkg_check_modules(EVDEV REQUIRED libevdev)
@ -97,6 +98,16 @@ if(FREESCALE_FOUND)
install(TARGETS moonlight-imx DESTINATION ${CMAKE_INSTALL_LIBDIR})
endif()
if(ROCKCHIP_FOUND)
list(APPEND MOONLIGHT_DEFINITIONS HAVE_ROCKCHIP)
list(APPEND MOONLIGHT_OPTIONS ROCKCHIP)
add_library(moonlight-rk SHARED ./src/video/rk.c)
target_include_directories(moonlight-rk PRIVATE ${ROCKCHIP_INCLUDE_DIRS} ${GAMESTREAM_INCLUDE_DIR} ${MOONLIGHT_COMMON_INCLUDE_DIR})
target_link_libraries(moonlight-rk gamestream ${ROCKCHIP_LIBRARIES})
set_property(TARGET moonlight-rk PROPERTY COMPILE_DEFINITIONS ${ROCKCHIP_DEFINITIONS})
install(TARGETS moonlight-rk DESTINATION ${CMAKE_INSTALL_LIBDIR})
endif()
if (SOFTWARE_FOUND)
target_sources(moonlight PRIVATE ./src/video/ffmpeg.c)
target_include_directories(moonlight PRIVATE ${AVCODEC_INCLUDE_DIRS} ${AVUTIL_INCLUDE_DIRS})
@ -147,12 +158,12 @@ if (PULSE_FOUND)
target_link_libraries(moonlight ${PULSE_LIBRARIES})
endif()
if (AMLOGIC_FOUND OR BROADCOM_FOUND OR FREESCALE_FOUND OR X11_FOUND)
if (AMLOGIC_FOUND OR BROADCOM_FOUND OR FREESCALE_FOUND OR ROCKCHIP_FOUND OR X11_FOUND)
list(APPEND MOONLIGHT_DEFINITIONS HAVE_EMBEDDED)
list(APPEND MOONLIGHT_OPTIONS EMBEDDED)
endif()
if(NOT AMLOGIC_FOUND AND NOT BROADCOM_FOUND AND NOT FREESCALE_FOUND AND NOT SOFTWARE_FOUND)
if(NOT AMLOGIC_FOUND AND NOT BROADCOM_FOUND AND NOT FREESCALE_FOUND AND NOT ROCKCHIP_FOUND AND NOT SOFTWARE_FOUND)
message(FATAL_ERROR "No video output available")
endif()

29
cmake/FindRockchip.cmake Normal file
View File

@ -0,0 +1,29 @@
find_path(DRM_INCLUDE_DIR
NAMES drm.h
DOC "libdrm include directory"
PATHS /usr/local/include/libdrm /usr/include/libdrm /usr/include)
mark_as_advanced(DRM_INCLUDE_DIR)
find_path(DRM_LIBRARY
NAMES libdrm.so
DOC "Path to libdrm Library"
PATHS /usr/local/lib /usr/lib /usr/lib/aarch64-linux-gnu /usr/lib/arm-linux-gnueabihf)
mark_as_advanced(DRM_INCLUDE_DIR)
find_path(ROCKCHIP_INCLUDE_DIR
NAMES rk_mpi.h
DOC "Rockchip include directory"
PATHS /usr/local/include/rockchip /usr/include/rockchip /usr/include)
mark_as_advanced(ROCKCHIP_INCLUDE_DIR)
find_library(ROCKCHIP_LIBRARY
NAMES librockchip_mpp.so
DOC "Path to Rockchip Media Process Platform Library"
PATHS /usr/local/lib /usr/lib /usr/lib/aarch64-linux-gnu /usr/lib/arm-linux-gnueabihf)
mark_as_advanced(ROCKCHIP_LIBRARY)
include(${CMAKE_ROOT}/Modules/FindPackageHandleStandardArgs.cmake)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(Rockchip DEFAULT_MSG ROCKCHIP_INCLUDE_DIR ROCKCHIP_LIBRARY)
set(ROCKCHIP_INCLUDE_DIRS ${ROCKCHIP_INCLUDE_DIR} ${DRM_INCLUDE_DIR})
set(ROCKCHIP_LIBRARIES ${ROCKCHIP_LIBRARY} ${DRM_LIBRARY})

View File

@ -46,6 +46,7 @@
## Select the audio and video decoder to use
## default - autodetect
## aml - hardware video decoder for ODROID-C1/C2
## rk - hardware video decoder for ODROID-N1 Rockchip
## omx - hardware video decoder for Raspberry Pi
## imx - hardware video decoder for i.MX6 devices
## x11 - software decoder

View File

@ -163,7 +163,7 @@ static void help() {
printf("\t-surround\t\tStream 5.1 surround sound (requires GFE 2.7)\n");
printf("\t-keydir <directory>\tLoad encryption keys from directory\n");
printf("\t-mapping <file>\t\tUse <file> as gamepad mappings configuration file\n");
printf("\t-platform <system>\tSpecify system used for audio, video and input: pi/imx/aml/x11/x11_vdpau/sdl/fake (default auto)\n");
printf("\t-platform <system>\tSpecify system used for audio, video and input: pi/imx/aml/rk/x11/x11_vdpau/sdl/fake (default auto)\n");
printf("\t-unsupported\t\tTry streaming if GFE version or options are unsupported\n");
#if defined(HAVE_SDL) || defined(HAVE_X11)
printf("\n WM options (SDL and X11 only)\n\n");

View File

@ -59,6 +59,13 @@ enum platform platform_check(char* name) {
return AML;
}
#endif
#ifdef HAVE_ROCKCHIP
if (std || strcmp(name, "rk") == 0) {
void *handle = dlopen("libmoonlight-rk.so", RTLD_NOW | RTLD_GLOBAL);
if (handle != NULL && dlsym(RTLD_DEFAULT, "mpp_init") != NULL)
return RK;
}
#endif
#ifdef HAVE_X11
bool x11 = strcmp(name, "x11") == 0;
bool vdpau = strcmp(name, "x11_vdpau") == 0;
@ -148,6 +155,10 @@ DECODER_RENDERER_CALLBACKS* platform_get_video(enum platform system) {
case AML:
return (PDECODER_RENDERER_CALLBACKS) dlsym(RTLD_DEFAULT, "decoder_callbacks_aml");
#endif
#ifdef HAVE_ROCKCHIP
case RK:
return (PDECODER_RENDERER_CALLBACKS) dlsym(RTLD_DEFAULT, "decoder_callbacks_rk");
#endif
}
return NULL;
}
@ -178,6 +189,7 @@ AUDIO_RENDERER_CALLBACKS* platform_get_audio(enum platform system, char* audio_d
bool platform_supports_hevc(enum platform system) {
switch (system) {
case AML:
case RK:
return true;
}
return false;
@ -191,6 +203,8 @@ char* platform_name(enum platform system) {
return "i.MX6 (MXC Vivante)";
case AML:
return "AMLogic VPU";
case RK:
return "Rockchip VPU";
case X11:
return "X Window System (software decoding)";
case X11_VAAPI:

View File

@ -26,7 +26,7 @@
#define IS_EMBEDDED(SYSTEM) SYSTEM != SDL
enum platform { NONE, SDL, X11, X11_VDPAU, X11_VAAPI, PI, IMX, AML, FAKE };
enum platform { NONE, SDL, X11, X11_VDPAU, X11_VAAPI, PI, IMX, AML, RK, FAKE };
enum platform platform_check(char*);
PDECODER_RENDERER_CALLBACKS platform_get_video(enum platform system);

484
src/video/rk.c Normal file
View File

@ -0,0 +1,484 @@
/*
* This file is part of Moonlight Embedded.
*
* Copyright (C) 2015-2018 Iwan Timmer
* Copyright (C) 2018 Martin Cerveny, Daniel Mehrwald
*
* Moonlight is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* Moonlight is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Moonlight; if not, see <http://www.gnu.org/licenses/>.
*/
#include <Limelight.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <pthread.h>
#include <unistd.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
#include <libdrm/drm_fourcc.h>
#include <rockchip/rk_mpi.h>
#define READ_BUF_SIZE (SZ_1M)
#define MAX_FRAMES 16
#define RK_H264 7
#define RK_H265 16777220
void *pkt_buf = NULL;
int fd;
int fb_id;
uint32_t plane_id, crtc_id;
int frm_eos;
int crtc_width;
int crtc_height;
RK_U32 frm_width;
RK_U32 frm_height;
int fb_x, fb_y, fb_width, fb_height;
pthread_t tid_frame, tid_display;
pthread_mutex_t mutex;
pthread_cond_t cond;
drmModePlane *ovr = NULL;
drmModeEncoder *encoder = NULL;
drmModeConnector *connector = NULL;
drmModeRes *resources = NULL;
drmModePlaneRes *plane_resources = NULL;
drmModeCrtcPtr crtc = {0};
MppCtx mpi_ctx;
MppApi *mpi_api;
MppPacket mpi_packet;
MppBufferGroup mpi_frm_grp;
struct {
int prime_fd;
int fb_id;
uint32_t handle;
} frame_to_drm[MAX_FRAMES];
void *display_thread(void *param) {
int ret;
while (!frm_eos) {
int _fb_id;
ret = pthread_mutex_lock(&mutex);
assert(!ret);
while (fb_id == 0) {
pthread_cond_wait(&cond, &mutex);
assert(!ret);
if (fb_id == 0 && frm_eos) {
ret = pthread_mutex_unlock(&mutex);
assert(!ret);
return NULL;
}
}
_fb_id = fb_id;
fb_id = 0;
ret = pthread_mutex_unlock(&mutex);
assert(!ret);
// show DRM FB in overlay plane (auto vsynced/atomic !)
ret = drmModeSetPlane(fd, plane_id, crtc_id, _fb_id, 0,
fb_x, fb_y, fb_width, fb_height,
0, 0, frm_width << 16, frm_height << 16);
assert(!ret);
}
}
void *frame_thread(void *param) {
int count = 0;
int ret;
int i;
MppFrame frame = NULL;
while (!frm_eos) {
ret = mpi_api->decode_get_frame(mpi_ctx, &frame);
if (ret != MPP_OK && ret != MPP_ERR_TIMEOUT) {
if (count < 3) {
fprintf(stderr, "Waiting for Frame (return code = %d, retry count = %d)\n", ret, count);
usleep(10000);
count++;
continue;
}
}
if (frame) {
if (mpp_frame_get_info_change(frame)) {
// new resolution
assert(!mpi_frm_grp);
frm_width = mpp_frame_get_width(frame);
frm_height = mpp_frame_get_height(frame);
RK_U32 hor_stride = mpp_frame_get_hor_stride(frame);
RK_U32 ver_stride = mpp_frame_get_ver_stride(frame);
MppFrameFormat fmt = mpp_frame_get_fmt(frame);
assert((fmt == MPP_FMT_YUV420SP) || (fmt == MPP_FMT_YUV420SP_10BIT));
// position overlay, scale to ratio
float crt_ratio = (float)crtc_width/crtc_height;
float frame_ratio = (float)frm_width/frm_height;
if (crt_ratio>frame_ratio) {
fb_width = frame_ratio/crt_ratio*crtc_width;
fb_height = crtc_height;
fb_x = (crtc_width-fb_width)/2;
fb_y = 0;
} else {
fb_width = crtc_width;
fb_height = crt_ratio/frame_ratio*crtc_height;
fb_x = 0;
fb_y = (crtc_height-fb_height)/2;
}
// create new external frame group and allocate (commit flow) new DRM buffers and DRM FB
assert(!mpi_frm_grp);
ret = mpp_buffer_group_get_external(&mpi_frm_grp, MPP_BUFFER_TYPE_DRM);
assert(!ret);
for (i = 0; i < MAX_FRAMES; i++) {
// new DRM buffer
struct drm_mode_create_dumb dmcd = {0};
dmcd.bpp = fmt == MPP_FMT_YUV420SP ? 8:10;
dmcd.width = hor_stride;
dmcd.height = ver_stride * 2; // documentation say not v*2/3 but v*2 (additional info included)
do {
ret = ioctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &dmcd);
} while (ret == -1 && (errno == EINTR || errno == EAGAIN));
assert(!ret);
assert(dmcd.pitch == (fmt == MPP_FMT_YUV420SP?hor_stride:hor_stride * 10 / 8));
assert(dmcd.size == (fmt == MPP_FMT_YUV420SP?hor_stride:hor_stride * 10 / 8) * ver_stride * 2);
frame_to_drm[i].handle = dmcd.handle;
// commit DRM buffer to frame group
struct drm_prime_handle dph = {0};
dph.handle = dmcd.handle;
dph.fd = -1;
do {
ret = ioctl(fd, DRM_IOCTL_PRIME_HANDLE_TO_FD, &dph);
} while (ret == -1 && (errno == EINTR || errno == EAGAIN));
assert(!ret);
MppBufferInfo info = {0};
info.type = MPP_BUFFER_TYPE_DRM;
info.size = dmcd.width * dmcd.height;
info.fd = dph.fd;
ret = mpp_buffer_commit(mpi_frm_grp, &info);
assert(!ret);
frame_to_drm[i].prime_fd = info.fd; // dups fd
// allocate DRM FB from DRM buffer
uint32_t handles[4] = {0}, pitches[4] = {0}, offsets[4] = {0};
handles[0] = frame_to_drm[i].handle;
offsets[0] = 0;
pitches[0] = hor_stride;
handles[1] = frame_to_drm[i].handle;
offsets[1] = hor_stride * ver_stride;
pitches[1] = hor_stride;
ret = drmModeAddFB2(fd, frm_width, frm_height, fmt == MPP_FMT_YUV420SP ? DRM_FORMAT_NV12:DRM_FORMAT_NV12_10, handles, pitches, offsets, &frame_to_drm[i].fb_id, 0);
assert(!ret);
}
// register external frame group
ret = mpi_api->control(mpi_ctx, MPP_DEC_SET_EXT_BUF_GROUP, mpi_frm_grp);
ret = mpi_api->control(mpi_ctx, MPP_DEC_SET_INFO_CHANGE_READY, NULL);
} else {
// regular frame received
MppBuffer buffer = mpp_frame_get_buffer(frame);
if (buffer) {
// find fb_id by frame prime_fd
MppBufferInfo info;
ret = mpp_buffer_info_get(buffer, &info);
assert(!ret);
for (i = 0; i < MAX_FRAMES; i++) {
if (frame_to_drm[i].prime_fd == info.fd) break;
}
assert(i != MAX_FRAMES);
// send DRM FB to display thread
ret = pthread_mutex_lock(&mutex);
assert(!ret);
fb_id = frame_to_drm[i].fb_id;
ret = pthread_cond_signal(&cond);
assert(!ret);
ret = pthread_mutex_unlock(&mutex);
assert(!ret);
} else {
fprintf(stderr, "Frame no buff\n");
}
}
frm_eos = mpp_frame_get_eos(frame);
mpp_frame_deinit(&frame);
frame = NULL;
} else {
if (!frm_eos) {
fprintf(stderr, "Didn't get frame from MPP (return code = %d)\n", ret);
}
break;
}
}
return NULL;
}
int rk_setup(int videoFormat, int width, int height, int redrawRate, void* context, int drFlags) {
int ret;
int i;
int j;
int format = 0;
switch (videoFormat) {
case VIDEO_FORMAT_H264:
format = RK_H264;
break;
case VIDEO_FORMAT_H265:
format = RK_H265;
break;
default:
fprintf(stderr, "Video format not supported\n");
return -1;
}
MppCodingType mpp_type = (MppCodingType)format;
ret = mpp_check_support_format(MPP_CTX_DEC, mpp_type);
assert(!ret);
fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC);
assert(fd >= 0);
resources = drmModeGetResources(fd);
assert(resources);
// find active monitor
for (i = 0; i < resources->count_connectors; ++i) {
connector = drmModeGetConnector(fd, resources->connectors[i]);
if (!connector) {
continue;
}
if (connector->connection == DRM_MODE_CONNECTED && connector->count_modes > 0) {
break;
}
drmModeFreeConnector(connector);
}
assert(i < resources->count_connectors);
for (i = 0; i < resources->count_encoders; ++i) {
encoder = drmModeGetEncoder(fd, resources->encoders[i]);
if (!encoder) {
continue;
}
if (encoder->encoder_id == connector->encoder_id) {
break;
}
drmModeFreeEncoder(encoder);
}
assert(i < resources->count_encoders);
for (i = 0; i < resources->count_crtcs; ++i) {
if (resources->crtcs[i] == encoder->crtc_id) {
crtc = drmModeGetCrtc(fd, resources->crtcs[i]);
assert(crtc);
break;
}
}
assert(i < resources->count_crtcs);
crtc_id = crtc->crtc_id;
crtc_width = crtc->width;
crtc_height = crtc->height;
uint32_t crtc_bit = (1 << i);
ret = drmSetClientCap(fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1);
assert(!ret);
plane_resources = drmModeGetPlaneResources(fd);
assert(plane_resources);
// search for OVERLAY (for active connector, unused, NV12 support)
for (i = 0; i < plane_resources->count_planes; i++) {
ovr = drmModeGetPlane(fd, plane_resources->planes[i]);
if (!ovr) {
continue;
}
for (j = 0; j < ovr->count_formats; j++) {
if (ovr->formats[j] == DRM_FORMAT_NV12) {
break;
}
}
if (j == ovr->count_formats) {
continue;
}
if ((ovr->possible_crtcs & crtc_bit) && !ovr->crtc_id) {
drmModeObjectPropertiesPtr props = drmModeObjectGetProperties(fd, plane_resources->planes[i], DRM_MODE_OBJECT_PLANE);
if (!props) {
continue;
}
for (j = 0; j < props->count_props && !plane_id; j++) {
drmModePropertyPtr prop = drmModeGetProperty(fd, props->props[j]);
if (!prop) {
continue;
}
if (!strcmp(prop->name, "type") && props->prop_values[j] == DRM_PLANE_TYPE_OVERLAY) {
plane_id = ovr->plane_id;
}
drmModeFreeProperty(prop);
}
drmModeFreeObjectProperties(props);
if (plane_id) {
break;
}
}
drmModeFreePlane(ovr);
}
assert(plane_id);
// disable cursor
drmModeSetCursor(fd, crtc_id, 0, 0, 0);
// MPI SETUP
pkt_buf = malloc(READ_BUF_SIZE);
assert(pkt_buf);
ret = mpp_packet_init(&mpi_packet, pkt_buf, READ_BUF_SIZE);
assert(!ret);
ret = mpp_create(&mpi_ctx, &mpi_api);
assert(!ret);
// decoder split mode (multi-data-input) need to be set before init
int param = 1;
ret = mpi_api->control(mpi_ctx, MPP_DEC_SET_PARSER_SPLIT_MODE, &param);
assert(!ret);
ret = mpp_init(mpi_ctx, MPP_CTX_DEC, mpp_type);
assert(!ret);
// set blocked read on Frame Thread
param = MPP_POLL_BLOCK;
ret = mpi_api->control(mpi_ctx, MPP_SET_OUTPUT_BLOCK, &param);
assert(!ret);
ret = pthread_mutex_init(&mutex, NULL);
assert(!ret);
ret = pthread_cond_init(&cond, NULL);
assert(!ret);
ret = pthread_create(&tid_frame, NULL, frame_thread, NULL);
assert(!ret);
ret = pthread_create(&tid_display, NULL, display_thread, NULL);
assert(!ret);
return 0;
}
void rk_cleanup() {
int i;
int ret;
frm_eos = 1;
ret = pthread_mutex_lock(&mutex);
assert(!ret);
ret = pthread_cond_signal(&cond);
assert(!ret);
ret = pthread_mutex_unlock(&mutex);
assert(!ret);
ret = pthread_join(tid_display, NULL);
assert(!ret);
ret = pthread_cond_destroy(&cond);
assert(!ret);
ret = pthread_mutex_destroy(&mutex);
assert(!ret);
ret = mpi_api->reset(mpi_ctx);
assert(!ret);
ret = pthread_join(tid_frame, NULL);
assert(!ret);
if (mpi_frm_grp) {
ret = mpp_buffer_group_put(mpi_frm_grp);
assert(!ret);
mpi_frm_grp = NULL;
for (i = 0; i < MAX_FRAMES; i++) {
ret = drmModeRmFB(fd, frame_to_drm[i].fb_id);
assert(!ret);
struct drm_mode_destroy_dumb dmdd = {0};
dmdd.handle = frame_to_drm[i].handle;
do {
ret = ioctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dmdd);
} while (ret == -1 && (errno == EINTR || errno == EAGAIN));
assert(!ret);
}
}
mpp_packet_deinit(&mpi_packet);
mpp_destroy(mpi_ctx);
free(pkt_buf);
drmModeFreePlane(ovr);
drmModeFreePlaneResources(plane_resources);
drmModeFreeEncoder(encoder);
drmModeFreeConnector(connector);
drmModeFreeCrtc(crtc);
drmModeFreeResources(resources);
close(fd);
}
int rk_submit_decode_unit(PDECODE_UNIT decodeUnit) {
int result = DR_OK;
if (decodeUnit->fullLength < READ_BUF_SIZE) {
PLENTRY entry = decodeUnit->bufferList;
int length = 0;
while (entry != NULL) {
memcpy(pkt_buf+length, entry->data, entry->length);
length += entry->length;
entry = entry->next;
}
if (length) {
mpp_packet_set_pos(mpi_packet, pkt_buf);
mpp_packet_set_length(mpi_packet, length);
while (MPP_OK != mpi_api->decode_put_packet(mpi_ctx, mpi_packet))
;
}
}
return result;
}
DECODER_RENDERER_CALLBACKS decoder_callbacks_rk = {
.setup = rk_setup,
.cleanup = rk_cleanup,
.submitDecodeUnit = rk_submit_decode_unit,
.capabilities = CAPABILITY_DIRECT_SUBMIT,
};