Initial commit
This commit is contained in:
@@ -0,0 +1 @@
|
||||
build/
|
||||
@@ -0,0 +1,57 @@
|
||||
cmake_minimum_required(VERSION 3.22.1)
|
||||
project(camera_hook LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
|
||||
# Find Android libraries
|
||||
find_library(log-lib log)
|
||||
find_library(dl-lib dl)
|
||||
find_library(mediandk-lib mediandk)
|
||||
find_library(android-lib android)
|
||||
|
||||
# HAL hook shared library
|
||||
add_library(camera_hook SHARED
|
||||
src/camera_wrapper.cpp
|
||||
src/video_decoder.cpp
|
||||
src/rtsp_client.cpp
|
||||
src/buffer_converter.cpp
|
||||
)
|
||||
|
||||
target_include_directories(camera_hook PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/include
|
||||
)
|
||||
|
||||
target_link_libraries(camera_hook
|
||||
${log-lib}
|
||||
${dl-lib}
|
||||
${mediandk-lib}
|
||||
${android-lib}
|
||||
)
|
||||
|
||||
# Compiler flags for LD_PRELOAD hook
|
||||
target_compile_options(camera_hook PRIVATE
|
||||
-Wall
|
||||
-Wextra
|
||||
-fvisibility=hidden
|
||||
-fPIC
|
||||
)
|
||||
|
||||
# Enable NEON intrinsics for ARM64
|
||||
if(ANDROID_ABI STREQUAL "arm64-v8a")
|
||||
target_compile_options(camera_hook PRIVATE -march=armv8-a+simd)
|
||||
endif()
|
||||
|
||||
# Output name
|
||||
set_target_properties(camera_hook PROPERTIES
|
||||
OUTPUT_NAME "camera_hook"
|
||||
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/../app/src/main/jniLibs/${ANDROID_ABI}"
|
||||
)
|
||||
|
||||
# Camera provider wrapper (static C binary, bind-mounted over original)
|
||||
add_executable(camera_provider_wrapper src/camera_provider_wrapper.c)
|
||||
target_link_options(camera_provider_wrapper PRIVATE -static)
|
||||
set_target_properties(camera_provider_wrapper PROPERTIES
|
||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/../root-module"
|
||||
)
|
||||
@@ -0,0 +1,72 @@
|
||||
#ifndef BUFFER_CONVERTER_H
|
||||
#define BUFFER_CONVERTER_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
|
||||
// Buffer format conversion utilities for HAL compatibility.
|
||||
// Converts between decoder output formats and HAL-expected formats.
|
||||
// Uses ARM NEON intrinsics for performance on Pixel 9a (arm64-v8a).
|
||||
|
||||
namespace buffer_converter {
|
||||
|
||||
// Convert YUV420 planar (I420) to NV21 (semi-planar, VU interleaved)
|
||||
// YUV420 planar: Y plane, U plane, V plane (separate)
|
||||
// NV21: Y plane, VU interleaved plane
|
||||
//
|
||||
// src_y, src_u, src_v: source plane pointers
|
||||
// src_y_stride, src_uv_stride: source plane strides (bytes per row)
|
||||
// dst_nv21: destination buffer (Y plane followed by VU interleaved)
|
||||
// dst_y_stride, dst_uv_stride: destination strides
|
||||
// width, height: frame dimensions
|
||||
//
|
||||
// Returns true on success, false on error
|
||||
bool yuv420_planar_to_nv21(
|
||||
const uint8_t* src_y, int src_y_stride,
|
||||
const uint8_t* src_u, int src_u_stride,
|
||||
const uint8_t* src_v, int src_v_stride,
|
||||
uint8_t* dst_nv21, int dst_y_stride, int dst_uv_stride,
|
||||
int width, int height);
|
||||
|
||||
// Convert YUV420 planar (I420) to YUV420 planar with different stride
|
||||
// Handles stride mismatch between decoder output and HAL buffer.
|
||||
// Copies Y plane with stride adjustment, then U and V planes.
|
||||
//
|
||||
// Returns true on success
|
||||
bool yuv420_planar_copy_with_stride(
|
||||
const uint8_t* src_y, int src_y_stride,
|
||||
const uint8_t* src_u, int src_u_stride,
|
||||
const uint8_t* src_v, int src_v_stride,
|
||||
uint8_t* dst_y, int dst_y_stride,
|
||||
uint8_t* dst_u, int dst_u_stride,
|
||||
uint8_t* dst_v, int dst_v_stride,
|
||||
int width, int height);
|
||||
|
||||
// Convert NV21 (semi-planar) to YUV420 planar (I420)
|
||||
// Used when decoder outputs NV21 but HAL expects planar.
|
||||
//
|
||||
// Returns true on success
|
||||
bool nv21_to_yuv420_planar(
|
||||
const uint8_t* src_nv21, int src_y_stride, int src_uv_stride,
|
||||
uint8_t* dst_y, int dst_y_stride,
|
||||
uint8_t* dst_u, int dst_u_stride,
|
||||
uint8_t* dst_v, int dst_v_stride,
|
||||
int width, int height);
|
||||
|
||||
// Fast Y plane copy with NEON (handles stride mismatch)
|
||||
// Copies width bytes per row, advancing by stride each row.
|
||||
void copy_plane_neon(
|
||||
const uint8_t* src, int src_stride,
|
||||
uint8_t* dst, int dst_stride,
|
||||
int width, int height);
|
||||
|
||||
// Interleave U and V planes into NV21 format (VU order) using NEON
|
||||
void interleave_uv_to_nv21_neon(
|
||||
const uint8_t* src_u, int src_u_stride,
|
||||
const uint8_t* src_v, int src_v_stride,
|
||||
uint8_t* dst_vu, int dst_vu_stride,
|
||||
int width, int height);
|
||||
|
||||
} // namespace buffer_converter
|
||||
|
||||
#endif // BUFFER_CONVERTER_H
|
||||
@@ -0,0 +1,34 @@
|
||||
#ifndef CAMERA_HAL_ICAMERA_DEVICE_H
|
||||
#define CAMERA_HAL_ICAMERA_DEVICE_H
|
||||
|
||||
#include "types.h"
|
||||
#include <cstdint>
|
||||
|
||||
namespace camera_hal {
|
||||
|
||||
// ICameraDevice AIDL v4 interface
|
||||
// Mirrors: hardware/interfaces/camera/device/aidl/android/hardware/camera/device/ICameraDevice.aidl
|
||||
class ICameraDevice {
|
||||
public:
|
||||
virtual ~ICameraDevice() = default;
|
||||
|
||||
virtual int32_t open(void* callback) = 0;
|
||||
virtual int32_t openInjectionSession(void* callback) = 0;
|
||||
virtual int32_t setTorchMode(bool enabled) = 0;
|
||||
virtual int32_t dumpState(int32_t fd) = 0;
|
||||
virtual int32_t getCameraCharacteristics(void* characteristics) = 0;
|
||||
virtual int32_t getPhysicalCameraCharacteristics(
|
||||
const char* physical_camera_id,
|
||||
void* characteristics) = 0;
|
||||
virtual int32_t close() = 0;
|
||||
};
|
||||
|
||||
// Function pointer types for dlsym
|
||||
typedef int32_t (*ICameraDevice_open_t)(void* self, void* callback);
|
||||
typedef int32_t (*ICameraDevice_close_t)(void* self);
|
||||
typedef int32_t (*ICameraDevice_getCameraCharacteristics_t)(
|
||||
void* self, void* characteristics);
|
||||
|
||||
} // namespace camera_hal
|
||||
|
||||
#endif // CAMERA_HAL_ICAMERA_DEVICE_H
|
||||
@@ -0,0 +1,60 @@
|
||||
#ifndef CAMERA_HAL_ICAMERA_DEVICE_SESSION_H
|
||||
#define CAMERA_HAL_ICAMERA_DEVICE_SESSION_H
|
||||
|
||||
#include "types.h"
|
||||
#include <cstdint>
|
||||
|
||||
namespace camera_hal {
|
||||
|
||||
// ICameraDeviceSession AIDL v4 interface
|
||||
// Mirrors: hardware/interfaces/camera/device/aidl/android/hardware/camera/device/ICameraDeviceSession.aidl
|
||||
class ICameraDeviceSession {
|
||||
public:
|
||||
virtual ~ICameraDeviceSession() = default;
|
||||
|
||||
virtual int32_t configureStreams(
|
||||
const Stream* streams,
|
||||
int32_t stream_count,
|
||||
StreamConfigurationMode mode,
|
||||
HalStreamConfiguration* out_config) = 0;
|
||||
|
||||
virtual int32_t processCaptureRequest(
|
||||
const CaptureRequest* requests,
|
||||
int32_t request_count,
|
||||
int32_t* out_num_request_processed) = 0;
|
||||
|
||||
virtual int32_t flush() = 0;
|
||||
virtual int32_t close() = 0;
|
||||
virtual int32_t signalStreamFlush(const int32_t* stream_ids, int32_t count) = 0;
|
||||
virtual int32_t getCaptureRequestMetadataQueue(void* queue) = 0;
|
||||
virtual int32_t getCaptureResultMetadataQueue(void* queue) = 0;
|
||||
virtual int32_t switchToOffline(
|
||||
const int32_t* streams_to_keep,
|
||||
int32_t count,
|
||||
void* out_offline_session) = 0;
|
||||
virtual int32_t isReconfigurationRequired(
|
||||
const void* old_session_params,
|
||||
const void* new_session_params,
|
||||
bool* out_required) = 0;
|
||||
};
|
||||
|
||||
// Function pointer types for dlsym
|
||||
typedef int32_t (*ICameraDeviceSession_configureStreams_t)(
|
||||
void* self,
|
||||
const Stream* streams,
|
||||
int32_t stream_count,
|
||||
StreamConfigurationMode mode,
|
||||
HalStreamConfiguration* out_config);
|
||||
|
||||
typedef int32_t (*ICameraDeviceSession_processCaptureRequest_t)(
|
||||
void* self,
|
||||
const CaptureRequest* requests,
|
||||
int32_t request_count,
|
||||
int32_t* out_num_request_processed);
|
||||
|
||||
typedef int32_t (*ICameraDeviceSession_flush_t)(void* self);
|
||||
typedef int32_t (*ICameraDeviceSession_close_t)(void* self);
|
||||
|
||||
} // namespace camera_hal
|
||||
|
||||
#endif // CAMERA_HAL_ICAMERA_DEVICE_SESSION_H
|
||||
@@ -0,0 +1,42 @@
|
||||
#ifndef CAMERA_HAL_ICAMERA_PROVIDER_H
|
||||
#define CAMERA_HAL_ICAMERA_PROVIDER_H
|
||||
|
||||
#include "types.h"
|
||||
#include <cstdint>
|
||||
|
||||
namespace camera_hal {
|
||||
|
||||
// ICameraProvider AIDL v3 interface
|
||||
// Mirrors: hardware/interfaces/camera/provider/aidl/android/hardware/camera/provider/ICameraProvider.aidl
|
||||
class ICameraProvider {
|
||||
public:
|
||||
virtual ~ICameraProvider() = default;
|
||||
|
||||
virtual int32_t setCallback(void* callback) = 0;
|
||||
virtual int32_t getCameraIdList(char*** camera_ids, int32_t* count) = 0;
|
||||
virtual int32_t getCameraDeviceInterface(
|
||||
const char* camera_id,
|
||||
void** out_device) = 0;
|
||||
virtual int32_t notifyDeviceStateChange(int64_t device_state) = 0;
|
||||
virtual int32_t getConcurrentStreamingCameraIds(
|
||||
char*** camera_ids, int32_t* count) = 0;
|
||||
virtual int32_t openSession(
|
||||
const char* camera_id,
|
||||
void* callback,
|
||||
void** out_session) = 0;
|
||||
virtual int32_t getVendorTags(void* tags) = 0;
|
||||
virtual int32_t getCameraCharacteristics(
|
||||
const char* camera_id,
|
||||
void* characteristics) = 0;
|
||||
};
|
||||
|
||||
typedef int32_t (*ICameraProvider_getCameraIdList_t)(
|
||||
void* self, char*** camera_ids, int32_t* count);
|
||||
typedef int32_t (*ICameraProvider_getCameraDeviceInterface_t)(
|
||||
void* self, const char* camera_id, void** out_device);
|
||||
typedef int32_t (*ICameraProvider_openSession_t)(
|
||||
void* self, const char* camera_id, void* callback, void** out_session);
|
||||
|
||||
} // namespace camera_hal
|
||||
|
||||
#endif // CAMERA_HAL_ICAMERA_PROVIDER_H
|
||||
@@ -0,0 +1,103 @@
|
||||
#ifndef CAMERA_HAL_TYPES_H
|
||||
#define CAMERA_HAL_TYPES_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
|
||||
// AIDL Camera HAL v4 common types
|
||||
// These mirror the AIDL interface types from hardware/interfaces/camera/
|
||||
|
||||
namespace camera_hal {
|
||||
|
||||
// Stream format constants (matching AIDL CameraMetadata)
|
||||
enum StreamFormat : int32_t {
|
||||
FORMAT_YUV_420_888 = 32,
|
||||
FORMAT_IMPLEMENTATION_DEFINED = 35,
|
||||
FORMAT_BLOB_JPEG = 37,
|
||||
FORMAT_RAW16 = 38,
|
||||
FORMAT_RAW_PRIVATE = 39,
|
||||
FORMAT_RAW10 = 40,
|
||||
FORMAT_RAW12 = 41,
|
||||
FORMAT_DEPTH16 = 42,
|
||||
FORMAT_DEPTH_POINT_CLOUD = 43,
|
||||
FORMAT_PRIVATE = 50,
|
||||
};
|
||||
|
||||
// Stream direction
|
||||
enum StreamDirection : int32_t {
|
||||
STREAM_OUTPUT = 0,
|
||||
STREAM_INPUT = 1,
|
||||
};
|
||||
|
||||
// Stream configuration
|
||||
struct Stream {
|
||||
int32_t id;
|
||||
int32_t width;
|
||||
int32_t height;
|
||||
StreamFormat format;
|
||||
StreamDirection direction;
|
||||
int32_t usage;
|
||||
int32_t rotation;
|
||||
int32_t data_space;
|
||||
void* physical_camera_id; // nullable
|
||||
};
|
||||
|
||||
// Buffer status
|
||||
enum BufferStatus : int32_t {
|
||||
BUFFER_STATUS_OK = 0,
|
||||
BUFFER_STATUS_ERROR = 1,
|
||||
BUFFER_STATUS_NO_BUFFER = 2,
|
||||
};
|
||||
|
||||
// Camera buffer descriptor (wraps AHardwareBuffer / GraphicBuffer)
|
||||
struct CameraBuffer {
|
||||
int32_t stream_id;
|
||||
int64_t buffer_id;
|
||||
void* handle; // AHardwareBuffer* or native_handle_t*
|
||||
int32_t status;
|
||||
int64_t timestamp;
|
||||
void* acquire_fence;
|
||||
void* release_fence;
|
||||
};
|
||||
|
||||
// Capture request
|
||||
struct CaptureRequest {
|
||||
int32_t frame_number;
|
||||
int32_t settings_count;
|
||||
void* settings; // CameraMetadata*
|
||||
int32_t input_buffer_present;
|
||||
CameraBuffer* input_buffer;
|
||||
int32_t output_buffer_count;
|
||||
CameraBuffer* output_buffers;
|
||||
int32_t physical_camera_id_count;
|
||||
void* physical_camera_ids;
|
||||
void* physical_camera_settings;
|
||||
};
|
||||
|
||||
// Capture result
|
||||
struct CaptureResult {
|
||||
int32_t frame_number;
|
||||
void* result; // CameraMetadata*
|
||||
int32_t output_buffer_count;
|
||||
CameraBuffer* output_buffers;
|
||||
int32_t physical_camera_metadata_count;
|
||||
void* physical_camera_ids;
|
||||
void* physical_camera_metadata;
|
||||
};
|
||||
|
||||
// Stream configuration mode
|
||||
enum StreamConfigurationMode : int32_t {
|
||||
NORMAL_MODE = 0,
|
||||
CONSTRAINED_HIGH_SPEED_MODE = 1,
|
||||
};
|
||||
|
||||
// HalStreamConfiguration (result of configureStreams)
|
||||
struct HalStreamConfiguration {
|
||||
int32_t stream_count;
|
||||
Stream* streams;
|
||||
StreamConfigurationMode mode;
|
||||
};
|
||||
|
||||
} // namespace camera_hal
|
||||
|
||||
#endif // CAMERA_HAL_TYPES_H
|
||||
@@ -0,0 +1,59 @@
|
||||
#ifndef RTSP_CLIENT_H
|
||||
#define RTSP_CLIENT_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
|
||||
namespace rtsp_client {
|
||||
|
||||
static constexpr int RTSP_DEFAULT_PORT = 554;
|
||||
static constexpr int RTP_BUFFER_SIZE = 65536;
|
||||
static constexpr int MAX_URL_LEN = 512;
|
||||
|
||||
struct RtspState {
|
||||
std::string url;
|
||||
std::string host;
|
||||
int port;
|
||||
std::string path;
|
||||
|
||||
int rtsp_fd;
|
||||
int rtp_fd;
|
||||
int rtcp_fd;
|
||||
int local_rtp_port;
|
||||
int local_rtcp_port;
|
||||
|
||||
std::string session_id;
|
||||
std::string control_url;
|
||||
std::string video_track_url;
|
||||
|
||||
bool connected;
|
||||
bool running;
|
||||
bool playing;
|
||||
|
||||
pthread_t receiver_thread;
|
||||
|
||||
int shmem_fd;
|
||||
uint8_t* shmem_base;
|
||||
size_t shmem_size;
|
||||
|
||||
int32_t width;
|
||||
int32_t height;
|
||||
|
||||
uint32_t frame_count;
|
||||
uint64_t bytes_received;
|
||||
};
|
||||
|
||||
int init_rtsp(const char* url);
|
||||
int connect_rtsp();
|
||||
int play_rtsp();
|
||||
void stop_rtsp();
|
||||
void release_rtsp();
|
||||
bool is_rtsp_connected();
|
||||
int get_rtsp_shmem_fd();
|
||||
int get_rtsp_width();
|
||||
int get_rtsp_height();
|
||||
|
||||
} // namespace rtsp_client
|
||||
|
||||
#endif // RTSP_CLIENT_H
|
||||
@@ -0,0 +1,100 @@
|
||||
#ifndef VIDEO_DECODER_H
|
||||
#define VIDEO_DECODER_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
|
||||
// Video decoder using NDK MediaCodec API
|
||||
// Decodes video files to YUV420 frames and writes to shared memory ring buffer
|
||||
// for consumption by the HAL hook in the camera provider process.
|
||||
|
||||
namespace video_decoder {
|
||||
|
||||
// Shared memory ring buffer header
|
||||
// This structure is placed at the beginning of the memfd shared memory region.
|
||||
struct RingBufferHeader {
|
||||
uint32_t magic; // 0xCAM2MAGC
|
||||
uint32_t version; // Header version (1)
|
||||
uint32_t frame_count; // Number of frame slots in ring buffer
|
||||
uint32_t frame_width; // Decoded frame width
|
||||
uint32_t frame_height; // Decoded frame height
|
||||
uint32_t frame_size; // Size of one YUV420 frame (w * h * 3/2)
|
||||
uint32_t write_index; // Atomic: next slot to write
|
||||
uint32_t read_index; // Atomic: last slot read by consumer
|
||||
uint32_t sequence; // Monotonically increasing frame counter
|
||||
uint32_t flags; // Bit flags (bit 0: decoder running)
|
||||
uint64_t last_timestamp; // Timestamp of last written frame (us)
|
||||
uint8_t reserved[216]; // Padding to 256 bytes
|
||||
};
|
||||
|
||||
static constexpr uint32_t RING_MAGIC = 0xCA22A61C;
|
||||
static constexpr uint32_t RING_VERSION = 1;
|
||||
static constexpr uint32_t RING_FRAME_COUNT = 4;
|
||||
static constexpr uint32_t RING_HEADER_SIZE = 256;
|
||||
static constexpr uint32_t FLAG_DECODER_RUNNING = 0x1;
|
||||
|
||||
// Calculate total shared memory size needed
|
||||
inline size_t calc_shmem_size(uint32_t width, uint32_t height, uint32_t frame_count = RING_FRAME_COUNT) {
|
||||
size_t frame_size = (size_t)width * height * 3 / 2; // YUV420
|
||||
return RING_HEADER_SIZE + (frame_size * frame_count);
|
||||
}
|
||||
|
||||
// Get pointer to frame slot in shared memory
|
||||
inline uint8_t* get_frame_ptr(uint8_t* base, uint32_t index, uint32_t frame_size, uint32_t frame_count) {
|
||||
return base + RING_HEADER_SIZE + ((index % frame_count) * frame_size);
|
||||
}
|
||||
|
||||
// Decoder state
|
||||
struct DecoderState {
|
||||
std::string video_path;
|
||||
int32_t width;
|
||||
int32_t height;
|
||||
int32_t rotation; // 0, 90, 180, 270
|
||||
bool loop;
|
||||
bool running;
|
||||
|
||||
// Shared memory
|
||||
int shmem_fd;
|
||||
uint8_t* shmem_base;
|
||||
size_t shmem_size;
|
||||
RingBufferHeader* header;
|
||||
|
||||
// Decoder thread
|
||||
pthread_t decoder_thread;
|
||||
};
|
||||
|
||||
// Initialize the video decoder
|
||||
// Returns 0 on success, negative on error
|
||||
int init_decoder(const char* video_path, bool loop = true);
|
||||
|
||||
// Start the decoder thread
|
||||
// Returns 0 on success
|
||||
int start_decoder();
|
||||
|
||||
// Stop the decoder thread
|
||||
void stop_decoder();
|
||||
|
||||
// Release decoder resources
|
||||
void release_decoder();
|
||||
|
||||
// Get the shared memory fd (for passing to other processes)
|
||||
// Returns -1 if not initialized
|
||||
int get_shmem_fd();
|
||||
|
||||
// Get decoder state info
|
||||
int get_decoder_width();
|
||||
int get_decoder_height();
|
||||
bool is_decoder_running();
|
||||
|
||||
// Write a YUV420 frame to the shared memory ring buffer
|
||||
// Called internally by the decoder thread
|
||||
// y_data, u_data, v_data: pointers to Y, U, V planes
|
||||
// y_stride, uv_stride: stride of Y and UV planes
|
||||
// width, height: frame dimensions
|
||||
bool write_frame_to_ring(const uint8_t* y_data, const uint8_t* u_data, const uint8_t* v_data,
|
||||
int y_stride, int uv_stride, int width, int height);
|
||||
|
||||
} // namespace video_decoder
|
||||
|
||||
#endif // VIDEO_DECODER_H
|
||||
@@ -0,0 +1,173 @@
|
||||
#include "buffer_converter.h"
|
||||
|
||||
#ifdef __aarch64__
|
||||
#include <arm_neon.h>
|
||||
#endif
|
||||
|
||||
#include <cstring>
|
||||
#include <algorithm>
|
||||
|
||||
namespace buffer_converter {
|
||||
|
||||
void copy_plane_neon(
|
||||
const uint8_t* src, int src_stride,
|
||||
uint8_t* dst, int dst_stride,
|
||||
int width, int height) {
|
||||
|
||||
#ifdef __aarch64__
|
||||
for (int row = 0; row < height; row++) {
|
||||
const uint8_t* src_row = src + row * src_stride;
|
||||
uint8_t* dst_row = dst + row * dst_stride;
|
||||
int col = 0;
|
||||
|
||||
for (; col + 15 < width; col += 16) {
|
||||
uint8x16_t data = vld1q_u8(src_row + col);
|
||||
vst1q_u8(dst_row + col, data);
|
||||
}
|
||||
for (; col < width; col++) {
|
||||
dst_row[col] = src_row[col];
|
||||
}
|
||||
}
|
||||
#else
|
||||
for (int row = 0; row < height; row++) {
|
||||
std::memcpy(dst + row * dst_stride, src + row * src_stride, width);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void interleave_uv_to_nv21_neon(
|
||||
const uint8_t* src_u, int src_u_stride,
|
||||
const uint8_t* src_v, int src_v_stride,
|
||||
uint8_t* dst_vu, int dst_vu_stride,
|
||||
int width, int height) {
|
||||
|
||||
int uv_width = width / 2;
|
||||
int uv_height = height / 2;
|
||||
|
||||
#ifdef __aarch64__
|
||||
for (int row = 0; row < uv_height; row++) {
|
||||
const uint8_t* u_row = src_u + row * src_u_stride;
|
||||
const uint8_t* v_row = src_v + row * src_v_stride;
|
||||
uint8_t* dst_row = dst_vu + row * dst_vu_stride;
|
||||
int col = 0;
|
||||
|
||||
for (; col + 15 < uv_width; col += 16) {
|
||||
uint8x16_t u_data = vld1q_u8(u_row + col);
|
||||
uint8x16_t v_data = vld1q_u8(v_row + col);
|
||||
uint8x16x2_t vu_interleaved;
|
||||
vu_interleaved.val[0] = v_data;
|
||||
vu_interleaved.val[1] = u_data;
|
||||
vst2q_u8(dst_row + col * 2, vu_interleaved);
|
||||
}
|
||||
for (; col < uv_width; col++) {
|
||||
dst_row[col * 2] = v_row[col];
|
||||
dst_row[col * 2 + 1] = u_row[col];
|
||||
}
|
||||
}
|
||||
#else
|
||||
for (int row = 0; row < uv_height; row++) {
|
||||
const uint8_t* u_row = src_u + row * src_u_stride;
|
||||
const uint8_t* v_row = src_v + row * src_v_stride;
|
||||
uint8_t* dst_row = dst_vu + row * dst_vu_stride;
|
||||
for (int col = 0; col < uv_width; col++) {
|
||||
dst_row[col * 2] = v_row[col];
|
||||
dst_row[col * 2 + 1] = u_row[col];
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool yuv420_planar_to_nv21(
|
||||
const uint8_t* src_y, int src_y_stride,
|
||||
const uint8_t* src_u, int src_u_stride,
|
||||
const uint8_t* src_v, int src_v_stride,
|
||||
uint8_t* dst_nv21, int dst_y_stride, int dst_uv_stride,
|
||||
int width, int height) {
|
||||
|
||||
if (!src_y || !src_u || !src_v || !dst_nv21 || width <= 0 || height <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
copy_plane_neon(src_y, src_y_stride, dst_nv21, dst_y_stride, width, height);
|
||||
|
||||
uint8_t* dst_vu = dst_nv21 + (dst_y_stride * height);
|
||||
interleave_uv_to_nv21_neon(src_u, src_u_stride, src_v, src_v_stride,
|
||||
dst_vu, dst_uv_stride, width, height);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool yuv420_planar_copy_with_stride(
|
||||
const uint8_t* src_y, int src_y_stride,
|
||||
const uint8_t* src_u, int src_u_stride,
|
||||
const uint8_t* src_v, int src_v_stride,
|
||||
uint8_t* dst_y, int dst_y_stride,
|
||||
uint8_t* dst_u, int dst_u_stride,
|
||||
uint8_t* dst_v, int dst_v_stride,
|
||||
int width, int height) {
|
||||
|
||||
if (!src_y || !src_u || !src_v || !dst_y || !dst_u || !dst_v ||
|
||||
width <= 0 || height <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
copy_plane_neon(src_y, src_y_stride, dst_y, dst_y_stride, width, height);
|
||||
|
||||
int uv_width = width / 2;
|
||||
int uv_height = height / 2;
|
||||
copy_plane_neon(src_u, src_u_stride, dst_u, dst_u_stride, uv_width, uv_height);
|
||||
copy_plane_neon(src_v, src_v_stride, dst_v, dst_v_stride, uv_width, uv_height);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool nv21_to_yuv420_planar(
|
||||
const uint8_t* src_nv21, int src_y_stride, int src_uv_stride,
|
||||
uint8_t* dst_y, int dst_y_stride,
|
||||
uint8_t* dst_u, int dst_u_stride,
|
||||
uint8_t* dst_v, int dst_v_stride,
|
||||
int width, int height) {
|
||||
|
||||
if (!src_nv21 || !dst_y || !dst_u || !dst_v || width <= 0 || height <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
copy_plane_neon(src_nv21, src_y_stride, dst_y, dst_y_stride, width, height);
|
||||
|
||||
int uv_width = width / 2;
|
||||
int uv_height = height / 2;
|
||||
const uint8_t* src_vu = src_nv21 + (src_y_stride * height);
|
||||
|
||||
#ifdef __aarch64__
|
||||
for (int row = 0; row < uv_height; row++) {
|
||||
const uint8_t* vu_row = src_vu + row * src_uv_stride;
|
||||
uint8_t* u_row = dst_u + row * dst_u_stride;
|
||||
uint8_t* v_row = dst_v + row * dst_v_stride;
|
||||
int col = 0;
|
||||
|
||||
for (; col + 15 < uv_width; col += 16) {
|
||||
uint8x16x2_t vu = vld2q_u8(vu_row + col * 2);
|
||||
vst1q_u8(v_row + col, vu.val[0]);
|
||||
vst1q_u8(u_row + col, vu.val[1]);
|
||||
}
|
||||
for (; col < uv_width; col++) {
|
||||
v_row[col] = vu_row[col * 2];
|
||||
u_row[col] = vu_row[col * 2 + 1];
|
||||
}
|
||||
}
|
||||
#else
|
||||
for (int row = 0; row < uv_height; row++) {
|
||||
const uint8_t* vu_row = src_vu + row * src_uv_stride;
|
||||
uint8_t* u_row = dst_u + row * dst_u_stride;
|
||||
uint8_t* v_row = dst_v + row * dst_v_stride;
|
||||
for (int col = 0; col < uv_width; col++) {
|
||||
v_row[col] = vu_row[col * 2];
|
||||
u_row[col] = vu_row[col * 2 + 1];
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace buffer_converter
|
||||
@@ -0,0 +1,15 @@
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
|
||||
#define REAL_PROVIDER "/data/adb/modules/camera-hook/camera-provider-real"
|
||||
#define HOOK_LIB "/data/adb/modules/camera-hook/libcamera_hook.so"
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
setenv("LD_PRELOAD", HOOK_LIB, 1);
|
||||
execv(REAL_PROVIDER, argv);
|
||||
fprintf(stderr, "CameraHook: execv(%s) failed: %s\n", REAL_PROVIDER, strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
@@ -0,0 +1,386 @@
|
||||
#include <dlfcn.h>
|
||||
#include <android/log.h>
|
||||
#include <unistd.h>
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <fstream>
|
||||
|
||||
#include "camera_hal/types.h"
|
||||
#include "camera_hal/ICameraProvider.h"
|
||||
#include "camera_hal/ICameraDevice.h"
|
||||
#include "camera_hal/ICameraDeviceSession.h"
|
||||
|
||||
#define LOG_TAG "CameraHook"
|
||||
#define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
|
||||
#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
|
||||
#define ALOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
|
||||
#define ALOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
|
||||
|
||||
static void* g_real_hal_handle = nullptr;
|
||||
static bool g_hook_initialized = false;
|
||||
|
||||
static const char* APEX_HAL_PATH =
|
||||
"/apex/com.google.pixel.camera.hal/lib64/libgooglecamerahal.so";
|
||||
|
||||
struct StreamInfo {
|
||||
int32_t id;
|
||||
int32_t width;
|
||||
int32_t height;
|
||||
int32_t format;
|
||||
int32_t direction;
|
||||
int32_t usage;
|
||||
int32_t data_space;
|
||||
int32_t hal_format;
|
||||
int32_t stride;
|
||||
};
|
||||
|
||||
struct HookState {
|
||||
std::map<std::string, void*> open_devices;
|
||||
std::map<void*, void*> device_sessions;
|
||||
bool virtual_camera_enabled = false;
|
||||
int32_t target_stream_format = camera_hal::FORMAT_YUV_420_888;
|
||||
|
||||
std::mutex stream_mutex;
|
||||
std::map<int32_t, StreamInfo> stream_registry;
|
||||
int32_t yuv_preview_stream_id = -1;
|
||||
int32_t yuv_preview_width = 0;
|
||||
int32_t yuv_preview_height = 0;
|
||||
|
||||
std::atomic<int64_t> frame_count{0};
|
||||
std::atomic<int64_t> injected_count{0};
|
||||
std::chrono::steady_clock::time_point fps_start;
|
||||
std::atomic<int64_t> fps_frame_count{0};
|
||||
|
||||
static constexpr const char* CONFIG_PATH = "/data/local/camera_magic/config.txt";
|
||||
};
|
||||
|
||||
static HookState g_hook_state;
|
||||
|
||||
static bool load_real_hal() {
|
||||
if (g_real_hal_handle) {
|
||||
return true;
|
||||
}
|
||||
|
||||
g_real_hal_handle = dlopen(APEX_HAL_PATH, RTLD_NOW | RTLD_LOCAL);
|
||||
if (!g_real_hal_handle) {
|
||||
ALOGE("Failed to load real HAL: %s", dlerror());
|
||||
return false;
|
||||
}
|
||||
|
||||
ALOGI("Loaded real HAL from %s", APEX_HAL_PATH);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void* resolve_real_symbol(const char* symbol) {
|
||||
if (!g_real_hal_handle) {
|
||||
return nullptr;
|
||||
}
|
||||
void* addr = dlsym(g_real_hal_handle, symbol);
|
||||
if (!addr) {
|
||||
ALOGW("Symbol not found in real HAL: %s", symbol);
|
||||
}
|
||||
return addr;
|
||||
}
|
||||
|
||||
static bool check_virtual_camera_enabled() {
|
||||
std::ifstream config(g_hook_state.CONFIG_PATH);
|
||||
if (!config.is_open()) {
|
||||
return false;
|
||||
}
|
||||
std::string line;
|
||||
while (std::getline(config, line)) {
|
||||
if (line.find("virtual_camera=1") != std::string::npos ||
|
||||
line.find("enabled=true") != std::string::npos) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool inject_video_frame(int32_t stream_id, void* buffer_handle,
|
||||
int32_t width, int32_t height) {
|
||||
// Task 7/9 will implement: read from shared memory ring buffer,
|
||||
// lock AHardwareBuffer, copy YUV data, unlock.
|
||||
// Returns true if frame was injected, false if no frame available.
|
||||
(void)stream_id;
|
||||
(void)buffer_handle;
|
||||
(void)width;
|
||||
(void)height;
|
||||
return false;
|
||||
}
|
||||
|
||||
static void update_fps_counter() {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(
|
||||
now - g_hook_state.fps_start).count();
|
||||
if (elapsed >= 1) {
|
||||
int64_t frames = g_hook_state.fps_frame_count.exchange(0);
|
||||
if (frames > 0) {
|
||||
ALOGI("[FPS] %.1f fps (%lld frames in %llds)",
|
||||
(double)frames / elapsed, (long long)frames, (long long)elapsed);
|
||||
}
|
||||
g_hook_state.fps_start = now;
|
||||
}
|
||||
}
|
||||
|
||||
class CameraDeviceSessionHook {
|
||||
public:
|
||||
void* real_session;
|
||||
|
||||
explicit CameraDeviceSessionHook(void* real)
|
||||
: real_session(real) {
|
||||
ALOGI("[SessionHook] Created for session %p", real);
|
||||
}
|
||||
|
||||
int32_t configureStreams(
|
||||
const camera_hal::Stream* streams,
|
||||
int32_t stream_count,
|
||||
camera_hal::StreamConfigurationMode mode,
|
||||
camera_hal::HalStreamConfiguration* out_config) {
|
||||
|
||||
ALOGI("[SessionHook] configureStreams: %d streams, mode=%d",
|
||||
stream_count, mode);
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_hook_state.stream_mutex);
|
||||
g_hook_state.stream_registry.clear();
|
||||
g_hook_state.yuv_preview_stream_id = -1;
|
||||
|
||||
for (int32_t i = 0; i < stream_count; i++) {
|
||||
const auto& s = streams[i];
|
||||
StreamInfo info;
|
||||
info.id = s.id;
|
||||
info.width = s.width;
|
||||
info.height = s.height;
|
||||
info.format = s.format;
|
||||
info.direction = s.direction;
|
||||
info.usage = s.usage;
|
||||
info.data_space = s.data_space;
|
||||
info.hal_format = 0;
|
||||
info.stride = 0;
|
||||
|
||||
g_hook_state.stream_registry[s.id] = info;
|
||||
|
||||
ALOGI("[SessionHook] Stream[%d]: id=%d %dx%d fmt=%d dir=%d usage=0x%x",
|
||||
i, s.id, s.width, s.height, s.format, s.direction, s.usage);
|
||||
|
||||
if (s.format == camera_hal::FORMAT_YUV_420_888 &&
|
||||
s.direction == camera_hal::STREAM_OUTPUT &&
|
||||
g_hook_state.yuv_preview_stream_id == -1) {
|
||||
g_hook_state.yuv_preview_stream_id = s.id;
|
||||
g_hook_state.yuv_preview_width = s.width;
|
||||
g_hook_state.yuv_preview_height = s.height;
|
||||
ALOGI("[SessionHook] -> YUV preview stream identified (id=%d)", s.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ALOGI("[SessionHook] Stream registry: %zu streams, YUV preview id=%d",
|
||||
g_hook_state.stream_registry.size(),
|
||||
g_hook_state.yuv_preview_stream_id);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t processCaptureRequest(
|
||||
const camera_hal::CaptureRequest* requests,
|
||||
int32_t request_count,
|
||||
int32_t* out_num_request_processed) {
|
||||
|
||||
int64_t total = g_hook_state.frame_count.fetch_add(request_count) + request_count;
|
||||
|
||||
bool enabled = check_virtual_camera_enabled();
|
||||
if (enabled != g_hook_state.virtual_camera_enabled) {
|
||||
g_hook_state.virtual_camera_enabled = enabled;
|
||||
ALOGI("[SessionHook] Virtual camera %s", enabled ? "ENABLED" : "DISABLED");
|
||||
}
|
||||
|
||||
for (int32_t r = 0; r < request_count; r++) {
|
||||
const auto& req = requests[r];
|
||||
|
||||
for (int32_t b = 0; b < req.output_buffer_count; b++) {
|
||||
const auto& buf = req.output_buffers[b];
|
||||
int32_t sid = buf.stream_id;
|
||||
|
||||
if (enabled && sid == g_hook_state.yuv_preview_stream_id) {
|
||||
StreamInfo* info = nullptr;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_hook_state.stream_mutex);
|
||||
auto it = g_hook_state.stream_registry.find(sid);
|
||||
if (it != g_hook_state.stream_registry.end()) {
|
||||
info = &it->second;
|
||||
}
|
||||
}
|
||||
|
||||
if (info && buf.handle) {
|
||||
bool injected = inject_video_frame(
|
||||
sid, buf.handle, info->width, info->height);
|
||||
if (injected) {
|
||||
g_hook_state.injected_count.fetch_add(1);
|
||||
g_hook_state.fps_frame_count.fetch_add(1);
|
||||
update_fps_counter();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (total % 300 == 0) {
|
||||
ALOGI("[SessionHook] Frames: %lld total, %lld injected",
|
||||
(long long)total,
|
||||
(long long)g_hook_state.injected_count.load());
|
||||
}
|
||||
}
|
||||
|
||||
if (out_num_request_processed) {
|
||||
*out_num_request_processed = request_count;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t flush() {
|
||||
ALOGI("[SessionHook] flush called");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t close() {
|
||||
ALOGI("[SessionHook] close called");
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
class CameraDeviceHook {
|
||||
public:
|
||||
void* real_device;
|
||||
std::string camera_id;
|
||||
|
||||
CameraDeviceHook(void* real, const char* id)
|
||||
: real_device(real), camera_id(id ? id : "unknown") {
|
||||
ALOGI("[DeviceHook] Created for device '%s' (%p)", camera_id.c_str(), real);
|
||||
}
|
||||
|
||||
int32_t open(void* callback) {
|
||||
ALOGI("[DeviceHook] open called for '%s'", camera_id.c_str());
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t getCameraCharacteristics(void* characteristics) {
|
||||
ALOGI("[DeviceHook] getCameraCharacteristics for '%s'", camera_id.c_str());
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t close() {
|
||||
ALOGI("[DeviceHook] close called for '%s'", camera_id.c_str());
|
||||
g_hook_state.open_devices.erase(camera_id);
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
class CameraProviderHook {
|
||||
public:
|
||||
void* real_provider;
|
||||
|
||||
explicit CameraProviderHook(void* real)
|
||||
: real_provider(real) {
|
||||
ALOGI("[ProviderHook] Created for provider %p", real);
|
||||
}
|
||||
|
||||
int32_t getCameraIdList(char*** camera_ids, int32_t* count) {
|
||||
ALOGI("[ProviderHook] getCameraIdList called");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t getCameraDeviceInterface(
|
||||
const char* camera_id,
|
||||
void** out_device) {
|
||||
ALOGI("[ProviderHook] getCameraDeviceInterface: '%s'", camera_id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t openSession(
|
||||
const char* camera_id,
|
||||
void* callback,
|
||||
void** out_session) {
|
||||
ALOGI("[ProviderHook] openSession: '%s'", camera_id);
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
__attribute__((constructor))
|
||||
static void camera_hook_init() {
|
||||
if (g_hook_initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
ALOGI("=== CameraHook LD_PRELOAD library loaded ===");
|
||||
ALOGI("PID: %d, Process: camera provider", getpid());
|
||||
|
||||
if (!load_real_hal()) {
|
||||
ALOGE("Cannot load real HAL, hook will not function");
|
||||
return;
|
||||
}
|
||||
|
||||
g_hook_initialized = true;
|
||||
ALOGI("CameraHook initialized successfully");
|
||||
}
|
||||
|
||||
__attribute__((destructor))
|
||||
static void camera_hook_deinit() {
|
||||
if (g_real_hal_handle) {
|
||||
dlclose(g_real_hal_handle);
|
||||
g_real_hal_handle = nullptr;
|
||||
}
|
||||
ALOGI("CameraHook unloaded");
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
__attribute__((visibility("default")))
|
||||
void* dlopen(const char* filename, int flags) {
|
||||
typedef void* (*real_dlopen_t)(const char*, int);
|
||||
static real_dlopen_t real_dlopen = nullptr;
|
||||
|
||||
if (!real_dlopen) {
|
||||
real_dlopen = (real_dlopen_t)::dlsym(RTLD_NEXT, "dlopen");
|
||||
}
|
||||
|
||||
if (filename && strstr(filename, "libgooglecamerahal.so")) {
|
||||
ALOGI("[dlopen] Intercepted: %s", filename);
|
||||
if (!load_real_hal()) {
|
||||
return nullptr;
|
||||
}
|
||||
return g_real_hal_handle;
|
||||
}
|
||||
|
||||
return real_dlopen(filename, flags);
|
||||
}
|
||||
|
||||
__attribute__((visibility("default")))
|
||||
void* dlsym(void* handle, const char* symbol) {
|
||||
typedef void* (*real_dlsym_t)(void*, const char*);
|
||||
static real_dlsym_t real_dlsym = nullptr;
|
||||
|
||||
if (!real_dlsym) {
|
||||
real_dlsym = (real_dlsym_t)::dlsym(RTLD_NEXT, "dlsym");
|
||||
}
|
||||
|
||||
void* result = real_dlsym(handle, symbol);
|
||||
|
||||
if (result && g_hook_initialized && symbol) {
|
||||
if (strstr(symbol, "CameraProvider") ||
|
||||
strstr(symbol, "CameraDevice") ||
|
||||
strstr(symbol, "createProvider") ||
|
||||
strstr(symbol, "getProvider")) {
|
||||
ALOGI("[dlsym] HAL symbol intercepted: %s -> %p", symbol, result);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
@@ -0,0 +1,15 @@
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
|
||||
#define REAL_CAMERASERVER "/data/adb/modules/camera-hook/cameraserver-real"
|
||||
#define HOOK_LIB "/data/local/camera_magic/libcamera_hook.so"
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
setenv("LD_PRELOAD", HOOK_LIB, 1);
|
||||
execv(REAL_CAMERASERVER, argv);
|
||||
fprintf(stderr, "CameraHook: execv(%s) failed: %s\n", REAL_CAMERASERVER, strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/ptrace.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/user.h>
|
||||
#include <sys/uio.h>
|
||||
#include <elf.h>
|
||||
#include <errno.h>
|
||||
|
||||
#define LIB_PATH "/data/local/tmp/libcamera_hook.so"
|
||||
#define ARM64_BRK 0xd4200000
|
||||
|
||||
static int ptrace_write_data(long pid, unsigned long addr, const void *buf, size_t len) {
|
||||
for (size_t i = 0; i < len; i += sizeof(long)) {
|
||||
long val = 0;
|
||||
size_t cpy = (len - i < sizeof(long)) ? len - i : sizeof(long);
|
||||
memcpy(&val, (const char*)buf + i, cpy);
|
||||
if (ptrace(PTRACE_POKETEXT, pid, addr + i, val) < 0) {
|
||||
fprintf(stderr, "POKETEXT failed at 0x%lx\n", addr + i);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static unsigned long find_linker_rx_base(long pid, unsigned long *file_offset_out) {
|
||||
char path[256];
|
||||
snprintf(path, sizeof(path), "/proc/%ld/maps", pid);
|
||||
FILE *f = fopen(path, "r");
|
||||
if (!f) { perror("fopen maps"); return 0; }
|
||||
|
||||
char line[1024];
|
||||
while (fgets(line, sizeof(line), f)) {
|
||||
if (strstr(line, "/apex/com.android.runtime/bin/linker64")) {
|
||||
unsigned long start, end, offset;
|
||||
char perm[8];
|
||||
sscanf(line, "%lx-%lx %4s %lx", &start, &end, perm, &offset);
|
||||
if (strstr(perm, "r-xp")) {
|
||||
fclose(f);
|
||||
fprintf(stderr, "linker64 r-xp: 0x%lx (file offset 0x%lx)\n", start, offset);
|
||||
if (file_offset_out) *file_offset_out = offset;
|
||||
return start;
|
||||
}
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
fprintf(stderr, "linker64 r-xp not found\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
if (argc < 2) {
|
||||
fprintf(stderr, "Usage: %s <pid>\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
long pid = atol(argv[1]);
|
||||
|
||||
unsigned long rx_offset = 0;
|
||||
unsigned long rx_base = find_linker_rx_base(pid, &rx_offset);
|
||||
if (!rx_base) {
|
||||
fprintf(stderr, "Failed to find linker64 r-xp\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
unsigned long dlopen_offset = 0x86fa0;
|
||||
unsigned long dlopen_addr = rx_base + (dlopen_offset - rx_offset);
|
||||
fprintf(stderr, "dlopen address: 0x%lx (rx_base=0x%lx, sym_offset=0x%lx, rx_offset=0x%lx)\n",
|
||||
dlopen_addr, rx_base, dlopen_offset, rx_offset);
|
||||
|
||||
fprintf(stderr, "Attaching to PID %ld...\n", pid);
|
||||
if (ptrace(PTRACE_ATTACH, pid, 0, 0) < 0) {
|
||||
perror("PTRACE_ATTACH");
|
||||
return 1;
|
||||
}
|
||||
int status;
|
||||
waitpid(pid, &status, WUNTRACED);
|
||||
if (!WIFSTOPPED(status)) {
|
||||
fprintf(stderr, "Target did not stop after attach\n");
|
||||
ptrace(PTRACE_DETACH, pid, 0, 0);
|
||||
return 1;
|
||||
}
|
||||
fprintf(stderr, "Attached, target stopped (signal=%d)\n", WSTOPSIG(status));
|
||||
|
||||
struct user_pt_regs saved_regs;
|
||||
struct iovec iov_save = { &saved_regs, sizeof(saved_regs) };
|
||||
if (ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, &iov_save) < 0) {
|
||||
perror("PTRACE_GETREGSET (save)");
|
||||
ptrace(PTRACE_DETACH, pid, 0, 0);
|
||||
return 1;
|
||||
}
|
||||
fprintf(stderr, "Saved: PC=0x%lx SP=0x%lx\n", saved_regs.pc, saved_regs.sp);
|
||||
|
||||
unsigned long data_addr = saved_regs.sp - 0x8000;
|
||||
fprintf(stderr, "Data area at 0x%lx\n", data_addr);
|
||||
|
||||
size_t path_len = strlen(LIB_PATH) + 1;
|
||||
if (ptrace_write_data(pid, data_addr, LIB_PATH, path_len) < 0) {
|
||||
fprintf(stderr, "Failed to write lib path\n");
|
||||
ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, &iov_save);
|
||||
ptrace(PTRACE_DETACH, pid, 0, 0);
|
||||
return 1;
|
||||
}
|
||||
fprintf(stderr, "Wrote '%s' to 0x%lx\n", LIB_PATH, data_addr);
|
||||
|
||||
unsigned long ret_addr = data_addr + 0x200;
|
||||
errno = 0;
|
||||
long orig_at_ret = ptrace(PTRACE_PEEKTEXT, pid, ret_addr, 0);
|
||||
if (errno) {
|
||||
perror("PEEKTEXT at return address");
|
||||
ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, &iov_save);
|
||||
ptrace(PTRACE_DETACH, pid, 0, 0);
|
||||
return 1;
|
||||
}
|
||||
if (ptrace(PTRACE_POKETEXT, pid, ret_addr, (void*)(long)ARM64_BRK) < 0) {
|
||||
perror("POKETEXT breakpoint");
|
||||
ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, &iov_save);
|
||||
ptrace(PTRACE_DETACH, pid, 0, 0);
|
||||
return 1;
|
||||
}
|
||||
fprintf(stderr, "Breakpoint at 0x%lx (orig=0x%lx)\n", ret_addr, orig_at_ret);
|
||||
|
||||
struct user_pt_regs call_regs = saved_regs;
|
||||
call_regs.regs[0] = data_addr;
|
||||
call_regs.regs[1] = 2;
|
||||
call_regs.regs[2] = saved_regs.regs[30];
|
||||
call_regs.regs[3] = 0;
|
||||
call_regs.regs[30] = ret_addr;
|
||||
call_regs.pc = dlopen_addr;
|
||||
|
||||
struct iovec iov_call = { &call_regs, sizeof(call_regs) };
|
||||
if (ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, &iov_call) < 0) {
|
||||
perror("PTRACE_SETREGSET (call)");
|
||||
ptrace(PTRACE_POKETEXT, pid, ret_addr, (void*)(long)orig_at_ret);
|
||||
ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, &iov_save);
|
||||
ptrace(PTRACE_DETACH, pid, 0, 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
fprintf(stderr, "Calling dlopen(\"%s\", 2) at 0x%lx...\n", LIB_PATH, dlopen_addr);
|
||||
ptrace(PTRACE_CONT, pid, 0, 0);
|
||||
waitpid(pid, &status, WUNTRACED);
|
||||
|
||||
if (WIFSTOPPED(status)) {
|
||||
fprintf(stderr, "Target stopped (signal=%d)\n", WSTOPSIG(status));
|
||||
} else if (WIFEXITED(status)) {
|
||||
fprintf(stderr, "Target exited! (code=%d)\n", WEXITSTATUS(status));
|
||||
} else if (WIFSIGNALED(status)) {
|
||||
fprintf(stderr, "Target killed! (signal=%d)\n", WTERMSIG(status));
|
||||
}
|
||||
|
||||
ptrace(PTRACE_POKETEXT, pid, ret_addr, (void*)(long)orig_at_ret);
|
||||
|
||||
struct user_pt_regs result_regs;
|
||||
struct iovec iov_result = { &result_regs, sizeof(result_regs) };
|
||||
if (ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, &iov_result) < 0) {
|
||||
perror("PTRACE_GETREGSET (result)");
|
||||
} else {
|
||||
fprintf(stderr, "dlopen returned: 0x%llx\n", (unsigned long long)result_regs.regs[0]);
|
||||
if (result_regs.regs[0] != 0) {
|
||||
fprintf(stderr, "SUCCESS! Library loaded.\n");
|
||||
} else {
|
||||
fprintf(stderr, "FAILED: dlopen returned NULL\n");
|
||||
}
|
||||
}
|
||||
|
||||
ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, &iov_save);
|
||||
ptrace(PTRACE_DETACH, pid, 0, 0);
|
||||
fprintf(stderr, "Detached from PID %ld\n", pid);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,653 @@
|
||||
#include "rtsp_client.h"
|
||||
#include "video_decoder.h"
|
||||
|
||||
#include <android/log.h>
|
||||
#include <media/NdkMediaCodec.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netdb.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <pthread.h>
|
||||
#include <cstring>
|
||||
#include <cerrno>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <poll.h>
|
||||
|
||||
#define LOG_TAG "RtspClient"
|
||||
#define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
|
||||
#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
|
||||
#define ALOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
|
||||
#define ALOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
|
||||
|
||||
static rtsp_client::RtspState g_rtsp = {};
|
||||
static AMediaCodec* g_codec = nullptr;
|
||||
static uint8_t g_rtp_buf[rtsp_client::RTP_BUFFER_SIZE];
|
||||
static uint8_t g_nal_buf[256 * 1024];
|
||||
static int g_nal_pos = 0;
|
||||
|
||||
static int memfd_create_compat(const char* name, unsigned int flags) {
|
||||
#ifdef __NR_memfd_create
|
||||
return (int)syscall(__NR_memfd_create, name, flags);
|
||||
#else
|
||||
(void)flags;
|
||||
char path[256];
|
||||
snprintf(path, sizeof(path), "/data/local/tmp/camera_magic_rtsp_%s_%d", name, getpid());
|
||||
int fd = open(path, O_CREAT | O_RDWR | O_TRUNC, 0666);
|
||||
if (fd >= 0) unlink(path);
|
||||
return fd;
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool parse_rtsp_url(const char* url, std::string& host, int& port, std::string& path) {
|
||||
const char* p = url;
|
||||
if (strncmp(p, "rtsp://", 7) != 0) return false;
|
||||
p += 7;
|
||||
|
||||
const char* colon = strchr(p, ':');
|
||||
const char* slash = strchr(p, '/');
|
||||
|
||||
if (colon && (!slash || colon < slash)) {
|
||||
host.assign(p, colon - p);
|
||||
port = atoi(colon + 1);
|
||||
p = colon + 1;
|
||||
while (*p >= '0' && *p <= '9') p++;
|
||||
} else {
|
||||
if (slash) {
|
||||
host.assign(p, slash - p);
|
||||
} else {
|
||||
host = p;
|
||||
}
|
||||
port = rtsp_client::RTSP_DEFAULT_PORT;
|
||||
}
|
||||
|
||||
if (*p == '/') {
|
||||
path = p;
|
||||
} else {
|
||||
path = "/";
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int rtsp_send_request(int fd, const char* method, const char* url,
|
||||
const char* extra_headers, int* cseq) {
|
||||
char req[2048];
|
||||
int len = snprintf(req, sizeof(req),
|
||||
"%s %s RTSP/1.0\r\n"
|
||||
"CSeq: %d\r\n"
|
||||
"%s"
|
||||
"\r\n",
|
||||
method, url, *cseq, extra_headers ? extra_headers : "");
|
||||
|
||||
(*cseq)++;
|
||||
|
||||
int sent = send(fd, req, len, MSG_NOSIGNAL);
|
||||
if (sent != len) {
|
||||
ALOGE("send failed: %d/%d (%s)", sent, len, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
ALOGD("Sent: %s CSeq=%d", method, *cseq - 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rtsp_read_response(int fd, char* buf, int buf_size, int* status_code,
|
||||
char* session_buf, int session_buf_size) {
|
||||
int total = 0;
|
||||
int timeout_ms = 5000;
|
||||
|
||||
struct pollfd pfd;
|
||||
pfd.fd = fd;
|
||||
pfd.events = POLLIN;
|
||||
|
||||
while (total < buf_size - 1) {
|
||||
int ret = poll(&pfd, 1, timeout_ms);
|
||||
if (ret <= 0) {
|
||||
ALOGE("poll timeout/error: %d", ret);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = recv(fd, buf + total, buf_size - 1 - total, 0);
|
||||
if (ret <= 0) {
|
||||
ALOGE("recv failed: %s", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
total += ret;
|
||||
buf[total] = '\0';
|
||||
|
||||
if (strstr(buf, "\r\n\r\n")) break;
|
||||
}
|
||||
|
||||
if (sscanf(buf, "RTSP/1.0 %d", status_code) != 1) {
|
||||
ALOGE("Cannot parse status from response");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (session_buf && session_buf_size > 0) {
|
||||
const char* sess = strstr(buf, "Session: ");
|
||||
if (sess) {
|
||||
sess += 9;
|
||||
int i = 0;
|
||||
while (*sess && *sess != '\r' && *sess != ';' && i < session_buf_size - 1) {
|
||||
session_buf[i++] = *sess++;
|
||||
}
|
||||
session_buf[i] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
ALOGD("Response: %d", *status_code);
|
||||
return total;
|
||||
}
|
||||
|
||||
static int create_udp_socket(int* port) {
|
||||
int fd = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
if (fd < 0) return -1;
|
||||
|
||||
struct sockaddr_in addr;
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
|
||||
for (int p = 50000; p < 65000; p += 2) {
|
||||
addr.sin_port = htons(p);
|
||||
if (bind(fd, (struct sockaddr*)&addr, sizeof(addr)) == 0) {
|
||||
*port = p;
|
||||
return fd;
|
||||
}
|
||||
}
|
||||
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static bool init_rtsp_shmem() {
|
||||
int w = g_rtsp.width > 0 ? g_rtsp.width : 1920;
|
||||
int h = g_rtsp.height > 0 ? g_rtsp.height : 1080;
|
||||
|
||||
size_t size = video_decoder::calc_shmem_size(w, h);
|
||||
int fd = memfd_create_compat("cam2magic_rtsp", 0);
|
||||
if (fd < 0) {
|
||||
ALOGE("memfd_create failed: %s", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ftruncate(fd, size) < 0) {
|
||||
ALOGE("ftruncate failed: %s", strerror(errno));
|
||||
close(fd);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t* base = (uint8_t*)mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
if (base == MAP_FAILED) {
|
||||
ALOGE("mmap failed: %s", strerror(errno));
|
||||
close(fd);
|
||||
return false;
|
||||
}
|
||||
|
||||
memset(base, 0, size);
|
||||
|
||||
auto* header = reinterpret_cast<video_decoder::RingBufferHeader*>(base);
|
||||
header->magic = video_decoder::RING_MAGIC;
|
||||
header->version = video_decoder::RING_VERSION;
|
||||
header->frame_count = video_decoder::RING_FRAME_COUNT;
|
||||
header->frame_width = w;
|
||||
header->frame_height = h;
|
||||
header->frame_size = (uint32_t)(w * h * 3 / 2);
|
||||
|
||||
g_rtsp.shmem_fd = fd;
|
||||
g_rtsp.shmem_base = base;
|
||||
g_rtsp.shmem_size = size;
|
||||
|
||||
ALOGI("RTSP shared memory: fd=%d, size=%zu, %dx%d", fd, size, w, h);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void release_rtsp_shmem() {
|
||||
if (g_rtsp.shmem_base && g_rtsp.shmem_base != MAP_FAILED) {
|
||||
munmap(g_rtsp.shmem_base, g_rtsp.shmem_size);
|
||||
g_rtsp.shmem_base = nullptr;
|
||||
}
|
||||
if (g_rtsp.shmem_fd >= 0) {
|
||||
close(g_rtsp.shmem_fd);
|
||||
g_rtsp.shmem_fd = -1;
|
||||
}
|
||||
g_rtsp.shmem_size = 0;
|
||||
}
|
||||
|
||||
static void feed_nal_to_decoder(const uint8_t* nal, int nal_len) {
|
||||
if (!g_codec || nal_len <= 0) return;
|
||||
|
||||
ssize_t idx = AMediaCodec_dequeueInputBuffer(g_codec, 1000);
|
||||
if (idx < 0) return;
|
||||
|
||||
size_t buf_size = 0;
|
||||
uint8_t* buf = AMediaCodec_getInputBuffer(g_codec, idx, &buf_size);
|
||||
if (!buf || (size_t)nal_len > buf_size) return;
|
||||
|
||||
memcpy(buf, nal, nal_len);
|
||||
|
||||
uint32_t flags = 0;
|
||||
if (nal[0] == 0 && nal[1] == 0 && nal[2] == 0 && nal[3] == 1) {
|
||||
int type = nal[4] & 0x1F;
|
||||
if (type == 5 || type == 7 || type == 8) {
|
||||
flags = AMEDIACODEC_BUFFER_FLAG_CODEC_CONFIG;
|
||||
}
|
||||
}
|
||||
|
||||
AMediaCodec_queueInputBuffer(g_codec, idx, 0, nal_len, 0, flags);
|
||||
}
|
||||
|
||||
static void drain_decoder_output() {
|
||||
if (!g_codec) return;
|
||||
|
||||
AMediaCodecBufferInfo info;
|
||||
ssize_t idx = AMediaCodec_dequeueOutputBuffer(g_codec, &info, 0);
|
||||
|
||||
if (idx >= 0) {
|
||||
size_t out_size = 0;
|
||||
uint8_t* out_buf = AMediaCodec_getOutputBuffer(g_codec, idx, &out_size);
|
||||
|
||||
if (out_buf && info.size > 0 && g_rtsp.shmem_base) {
|
||||
int w = g_rtsp.width;
|
||||
int h = g_rtsp.height;
|
||||
|
||||
if (w > 0 && h > 0 && out_size >= (size_t)(w * h * 3 / 2)) {
|
||||
const uint8_t* y = out_buf;
|
||||
const uint8_t* u = out_buf + w * h;
|
||||
const uint8_t* v = u + (w / 2) * (h / 2);
|
||||
|
||||
video_decoder::write_frame_to_ring(y, u, v, w, w / 2, w, h);
|
||||
g_rtsp.frame_count++;
|
||||
|
||||
if (g_rtsp.frame_count % 300 == 0) {
|
||||
ALOGI("RTSP decoded %u frames", g_rtsp.frame_count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AMediaCodec_releaseOutputBuffer(g_codec, idx, false);
|
||||
} else if (idx == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) {
|
||||
AMediaFormat* fmt = AMediaCodec_getOutputFormat(g_codec);
|
||||
int32_t w = 0, h = 0;
|
||||
AMediaFormat_getInt32(fmt, AMEDIAFORMAT_KEY_WIDTH, &w);
|
||||
AMediaFormat_getInt32(fmt, AMEDIAFORMAT_KEY_HEIGHT, &h);
|
||||
ALOGI("RTSP output format changed: %dx%d", w, h);
|
||||
if (w > 0 && h > 0) {
|
||||
g_rtsp.width = w;
|
||||
g_rtsp.height = h;
|
||||
}
|
||||
AMediaFormat_delete(fmt);
|
||||
}
|
||||
}
|
||||
|
||||
static void* rtsp_receiver_thread(void* arg) {
|
||||
(void)arg;
|
||||
ALOGI("RTSP receiver thread started");
|
||||
|
||||
while (g_rtsp.running) {
|
||||
struct pollfd pfd;
|
||||
pfd.fd = g_rtsp.rtp_fd;
|
||||
pfd.events = POLLIN;
|
||||
|
||||
int ret = poll(&pfd, 1, 1000);
|
||||
if (ret <= 0) continue;
|
||||
|
||||
ret = recv(g_rtsp.rtp_fd, g_rtp_buf, sizeof(g_rtp_buf), 0);
|
||||
if (ret <= 0) continue;
|
||||
|
||||
if (ret < 12) continue;
|
||||
|
||||
int payload_type = g_rtp_buf[1] & 0x7F;
|
||||
|
||||
if (payload_type != 96 && payload_type != 97 && payload_type != 98 && payload_type != 99 &&
|
||||
payload_type != 100 && payload_type != 101 && payload_type != 102 && payload_type != 103 &&
|
||||
payload_type != 104 && payload_type != 105 && payload_type != 106 && payload_type != 107 &&
|
||||
payload_type != 108 && payload_type != 109 && payload_type != 110 && payload_type != 111 &&
|
||||
payload_type != 112 && payload_type != 113 && payload_type != 114 && payload_type != 115 &&
|
||||
payload_type != 116 && payload_type != 117 && payload_type != 118 && payload_type != 119 &&
|
||||
payload_type != 120 && payload_type != 121 && payload_type != 122 && payload_type != 123 &&
|
||||
payload_type != 124 && payload_type != 125 && payload_type != 126 && payload_type != 127) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const uint8_t* payload = g_rtp_buf + 12;
|
||||
int payload_len = ret - 12;
|
||||
|
||||
if (payload_len < 2) continue;
|
||||
|
||||
int nal_type = payload[0] & 0x1F;
|
||||
|
||||
if (nal_type == 28 || nal_type == 29) {
|
||||
int fu_header = payload[1];
|
||||
int start = fu_header & 0x80;
|
||||
int end = fu_header & 0x40;
|
||||
int nal_hdr = (payload[0] & 0xE0) | (fu_header & 0x1F);
|
||||
|
||||
if (start) {
|
||||
g_nal_buf[0] = 0;
|
||||
g_nal_buf[1] = 0;
|
||||
g_nal_buf[2] = 0;
|
||||
g_nal_buf[3] = 1;
|
||||
g_nal_buf[4] = nal_hdr;
|
||||
g_nal_pos = 5;
|
||||
|
||||
int copy_len = payload_len - 2;
|
||||
if (g_nal_pos + copy_len < (int)sizeof(g_nal_buf)) {
|
||||
memcpy(g_nal_buf + g_nal_pos, payload + 2, copy_len);
|
||||
g_nal_pos += copy_len;
|
||||
}
|
||||
} else {
|
||||
int copy_len = payload_len - 2;
|
||||
if (g_nal_pos + copy_len < (int)sizeof(g_nal_buf)) {
|
||||
memcpy(g_nal_buf + g_nal_pos, payload + 2, copy_len);
|
||||
g_nal_pos += copy_len;
|
||||
}
|
||||
}
|
||||
|
||||
if (end) {
|
||||
feed_nal_to_decoder(g_nal_buf, g_nal_pos);
|
||||
drain_decoder_output();
|
||||
g_nal_pos = 0;
|
||||
g_rtsp.bytes_received += ret;
|
||||
}
|
||||
} else if (nal_type >= 1 && nal_type <= 23) {
|
||||
g_nal_buf[0] = 0;
|
||||
g_nal_buf[1] = 0;
|
||||
g_nal_buf[2] = 0;
|
||||
g_nal_buf[3] = 1;
|
||||
|
||||
int copy_len = payload_len;
|
||||
if (4 + copy_len < (int)sizeof(g_nal_buf)) {
|
||||
memcpy(g_nal_buf + 4, payload, copy_len);
|
||||
feed_nal_to_decoder(g_nal_buf, 4 + copy_len);
|
||||
drain_decoder_output();
|
||||
g_rtsp.bytes_received += ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ALOGI("RTSP receiver thread finished");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int rtsp_client::init_rtsp(const char* url) {
|
||||
if (!url) return -1;
|
||||
|
||||
memset(&g_rtsp, 0, sizeof(g_rtsp));
|
||||
g_rtsp.url = url;
|
||||
g_rtsp.rtsp_fd = -1;
|
||||
g_rtsp.rtp_fd = -1;
|
||||
g_rtsp.rtcp_fd = -1;
|
||||
g_rtsp.shmem_fd = -1;
|
||||
g_rtsp.width = 1920;
|
||||
g_rtsp.height = 1080;
|
||||
|
||||
if (!parse_rtsp_url(url, g_rtsp.host, g_rtsp.port, g_rtsp.path)) {
|
||||
ALOGE("Invalid RTSP URL: %s", url);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ALOGI("RTSP init: %s:%d%s", g_rtsp.host.c_str(), g_rtsp.port, g_rtsp.path.c_str());
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rtsp_client::connect_rtsp() {
|
||||
int cseq = 1;
|
||||
char resp_buf[4096];
|
||||
int status = 0;
|
||||
char session_buf[256] = {0};
|
||||
|
||||
struct sockaddr_in server;
|
||||
memset(&server, 0, sizeof(server));
|
||||
server.sin_family = AF_INET;
|
||||
server.sin_port = htons(g_rtsp.port);
|
||||
|
||||
struct hostent* he = gethostbyname(g_rtsp.host.c_str());
|
||||
if (!he) {
|
||||
ALOGE("Cannot resolve %s", g_rtsp.host.c_str());
|
||||
return -1;
|
||||
}
|
||||
memcpy(&server.sin_addr, he->h_addr, he->h_length);
|
||||
|
||||
g_rtsp.rtsp_fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (g_rtsp.rtsp_fd < 0) {
|
||||
ALOGE("socket failed: %s", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (connect(g_rtsp.rtsp_fd, (struct sockaddr*)&server, sizeof(server)) < 0) {
|
||||
ALOGE("connect failed: %s", strerror(errno));
|
||||
close(g_rtsp.rtsp_fd);
|
||||
g_rtsp.rtsp_fd = -1;
|
||||
return -1;
|
||||
}
|
||||
|
||||
ALOGI("Connected to %s:%d", g_rtsp.host.c_str(), g_rtsp.port);
|
||||
|
||||
char desc_url[1024];
|
||||
snprintf(desc_url, sizeof(desc_url), "rtsp://%s:%d%s",
|
||||
g_rtsp.host.c_str(), g_rtsp.port, g_rtsp.path.c_str());
|
||||
|
||||
if (rtsp_send_request(g_rtsp.rtsp_fd, "DESCRIBE", desc_url,
|
||||
"Accept: application/sdp\r\n", &cseq) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (rtsp_read_response(g_rtsp.rtsp_fd, resp_buf, sizeof(resp_buf), &status,
|
||||
session_buf, sizeof(session_buf)) < 0 || status != 200) {
|
||||
ALOGE("DESCRIBE failed: %d", status);
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char* control = strstr(resp_buf, "a=control:");
|
||||
if (control) {
|
||||
control += 10;
|
||||
while (*control == ' ') control++;
|
||||
char* end = strchr((char*)control, '\r');
|
||||
if (end) {
|
||||
g_rtsp.control_url.assign(control, end - control);
|
||||
ALOGI("Control URL: %s", g_rtsp.control_url.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
const char* sdp_w = strstr(resp_buf, "a=framesize:");
|
||||
if (sdp_w) {
|
||||
int w = 0, h = 0;
|
||||
if (sscanf(sdp_w, "a=framesize:%*d %d-%d", &w, &h) == 2 && w > 0 && h > 0) {
|
||||
g_rtsp.width = w;
|
||||
g_rtsp.height = h;
|
||||
ALOGI("SDP resolution: %dx%d", w, h);
|
||||
}
|
||||
}
|
||||
|
||||
g_rtsp.rtp_fd = create_udp_socket(&g_rtsp.local_rtp_port);
|
||||
if (g_rtsp.rtp_fd < 0) {
|
||||
ALOGE("Cannot create RTP socket");
|
||||
return -1;
|
||||
}
|
||||
|
||||
g_rtsp.local_rtcp_port = g_rtsp.local_rtp_port + 1;
|
||||
|
||||
char transport[512];
|
||||
snprintf(transport, sizeof(transport),
|
||||
"Transport: RTP/AVP/UDP;unicast;client_port=%d-%d\r\n",
|
||||
g_rtsp.local_rtp_port, g_rtsp.local_rtcp_port);
|
||||
|
||||
char setup_url[1024];
|
||||
if (!g_rtsp.control_url.empty() && g_rtsp.control_url.find("rtsp://") == 0) {
|
||||
snprintf(setup_url, sizeof(setup_url), "%s", g_rtsp.control_url.c_str());
|
||||
} else if (!g_rtsp.control_url.empty()) {
|
||||
snprintf(setup_url, sizeof(setup_url), "rtsp://%s:%d/%s",
|
||||
g_rtsp.host.c_str(), g_rtsp.port, g_rtsp.control_url.c_str());
|
||||
} else {
|
||||
snprintf(setup_url, sizeof(setup_url), "%s", desc_url);
|
||||
}
|
||||
|
||||
if (rtsp_send_request(g_rtsp.rtsp_fd, "SETUP", setup_url, transport, &cseq) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
memset(session_buf, 0, sizeof(session_buf));
|
||||
if (rtsp_read_response(g_rtsp.rtsp_fd, resp_buf, sizeof(resp_buf), &status,
|
||||
session_buf, sizeof(session_buf)) < 0 || status != 200) {
|
||||
ALOGE("SETUP failed: %d", status);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (session_buf[0]) {
|
||||
g_rtsp.session_id = session_buf;
|
||||
ALOGI("Session: %s", g_rtsp.session_id.c_str());
|
||||
}
|
||||
|
||||
if (!init_rtsp_shmem()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char* mime = "video/avc";
|
||||
g_codec = AMediaCodec_createDecoderByType(mime);
|
||||
if (!g_codec) {
|
||||
ALOGE("Cannot create MediaCodec decoder");
|
||||
return -1;
|
||||
}
|
||||
|
||||
AMediaFormat* fmt = AMediaFormat_new();
|
||||
AMediaFormat_setString(fmt, AMEDIAFORMAT_KEY_MIME, mime);
|
||||
AMediaFormat_setInt32(fmt, AMEDIAFORMAT_KEY_WIDTH, g_rtsp.width);
|
||||
AMediaFormat_setInt32(fmt, AMEDIAFORMAT_KEY_HEIGHT, g_rtsp.height);
|
||||
|
||||
media_status_t st = AMediaCodec_configure(g_codec, fmt, nullptr, nullptr, 0);
|
||||
AMediaFormat_delete(fmt);
|
||||
if (st != AMEDIA_OK) {
|
||||
ALOGE("AMediaCodec_configure failed: %d", st);
|
||||
AMediaCodec_delete(g_codec);
|
||||
g_codec = nullptr;
|
||||
return -1;
|
||||
}
|
||||
|
||||
st = AMediaCodec_start(g_codec);
|
||||
if (st != AMEDIA_OK) {
|
||||
ALOGE("AMediaCodec_start failed: %d", st);
|
||||
AMediaCodec_delete(g_codec);
|
||||
g_codec = nullptr;
|
||||
return -1;
|
||||
}
|
||||
|
||||
g_rtsp.connected = true;
|
||||
ALOGI("RTSP connected, codec started");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rtsp_client::play_rtsp() {
|
||||
if (!g_rtsp.connected) return -1;
|
||||
|
||||
int cseq = 4;
|
||||
char resp_buf[4096];
|
||||
int status = 0;
|
||||
char session_buf[256] = {0};
|
||||
|
||||
char play_url[1024];
|
||||
if (!g_rtsp.control_url.empty() && g_rtsp.control_url.find("rtsp://") == 0) {
|
||||
snprintf(play_url, sizeof(play_url), "%s", g_rtsp.control_url.c_str());
|
||||
} else {
|
||||
snprintf(play_url, sizeof(play_url), "rtsp://%s:%d%s",
|
||||
g_rtsp.host.c_str(), g_rtsp.port, g_rtsp.path.c_str());
|
||||
}
|
||||
|
||||
char session_hdr[512];
|
||||
snprintf(session_hdr, sizeof(session_hdr), "Session: %s\r\n", g_rtsp.session_id.c_str());
|
||||
|
||||
if (rtsp_send_request(g_rtsp.rtsp_fd, "PLAY", play_url, session_hdr, &cseq) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (rtsp_read_response(g_rtsp.rtsp_fd, resp_buf, sizeof(resp_buf), &status,
|
||||
session_buf, sizeof(session_buf)) < 0 || status != 200) {
|
||||
ALOGE("PLAY failed: %d", status);
|
||||
return -1;
|
||||
}
|
||||
|
||||
g_rtsp.running = true;
|
||||
g_rtsp.playing = true;
|
||||
|
||||
int ret = pthread_create(&g_rtsp.receiver_thread, nullptr, rtsp_receiver_thread, nullptr);
|
||||
if (ret != 0) {
|
||||
ALOGE("pthread_create failed: %s", strerror(ret));
|
||||
g_rtsp.running = false;
|
||||
return -1;
|
||||
}
|
||||
|
||||
ALOGI("RTSP playing, receiver thread started");
|
||||
return 0;
|
||||
}
|
||||
|
||||
void rtsp_client::stop_rtsp() {
|
||||
if (!g_rtsp.running) return;
|
||||
|
||||
ALOGI("Stopping RTSP");
|
||||
g_rtsp.running = false;
|
||||
g_rtsp.playing = false;
|
||||
|
||||
if (g_rtsp.receiver_thread) {
|
||||
pthread_join(g_rtsp.receiver_thread, nullptr);
|
||||
g_rtsp.receiver_thread = 0;
|
||||
}
|
||||
|
||||
if (g_codec) {
|
||||
AMediaCodec_stop(g_codec);
|
||||
AMediaCodec_delete(g_codec);
|
||||
g_codec = nullptr;
|
||||
}
|
||||
|
||||
if (g_rtsp.rtsp_fd >= 0) {
|
||||
int cseq = 10;
|
||||
char teardown_url[1024];
|
||||
snprintf(teardown_url, sizeof(teardown_url), "rtsp://%s:%d%s",
|
||||
g_rtsp.host.c_str(), g_rtsp.port, g_rtsp.path.c_str());
|
||||
char session_hdr[512];
|
||||
snprintf(session_hdr, sizeof(session_hdr), "Session: %s\r\n", g_rtsp.session_id.c_str());
|
||||
rtsp_send_request(g_rtsp.rtsp_fd, "TEARDOWN", teardown_url, session_hdr, &cseq);
|
||||
close(g_rtsp.rtsp_fd);
|
||||
g_rtsp.rtsp_fd = -1;
|
||||
}
|
||||
|
||||
if (g_rtsp.rtp_fd >= 0) {
|
||||
close(g_rtsp.rtp_fd);
|
||||
g_rtsp.rtp_fd = -1;
|
||||
}
|
||||
|
||||
release_rtsp_shmem();
|
||||
|
||||
g_rtsp.connected = false;
|
||||
ALOGI("RTSP stopped");
|
||||
}
|
||||
|
||||
void rtsp_client::release_rtsp() {
|
||||
stop_rtsp();
|
||||
g_rtsp.url.clear();
|
||||
g_rtsp.host.clear();
|
||||
g_rtsp.path.clear();
|
||||
g_rtsp.session_id.clear();
|
||||
g_rtsp.control_url.clear();
|
||||
ALOGI("RTSP released");
|
||||
}
|
||||
|
||||
bool rtsp_client::is_rtsp_connected() {
|
||||
return g_rtsp.connected && g_rtsp.playing;
|
||||
}
|
||||
|
||||
int rtsp_client::get_rtsp_shmem_fd() {
|
||||
return g_rtsp.shmem_fd;
|
||||
}
|
||||
|
||||
int rtsp_client::get_rtsp_width() {
|
||||
return g_rtsp.width;
|
||||
}
|
||||
|
||||
int rtsp_client::get_rtsp_height() {
|
||||
return g_rtsp.height;
|
||||
}
|
||||
@@ -0,0 +1,401 @@
|
||||
#include "video_decoder.h"
|
||||
|
||||
#include <android/log.h>
|
||||
#include <media/NdkMediaCodec.h>
|
||||
#include <media/NdkMediaExtractor.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <pthread.h>
|
||||
#include <cstring>
|
||||
#include <cerrno>
|
||||
|
||||
#define LOG_TAG "VideoDecoder"
|
||||
#define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
|
||||
#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
|
||||
#define ALOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
|
||||
#define ALOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
|
||||
|
||||
static video_decoder::DecoderState g_decoder = {};
|
||||
|
||||
static int memfd_create_compat(const char* name, unsigned int flags) {
|
||||
#ifdef __NR_memfd_create
|
||||
return (int)syscall(__NR_memfd_create, name, flags);
|
||||
#else
|
||||
(void)flags;
|
||||
char path[256];
|
||||
snprintf(path, sizeof(path), "/data/local/tmp/camera_magic_shmem_%s_%d", name, getpid());
|
||||
int fd = open(path, O_CREAT | O_RDWR | O_TRUNC, 0666);
|
||||
if (fd >= 0) unlink(path);
|
||||
return fd;
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool init_shmem(uint32_t width, uint32_t height) {
|
||||
size_t size = video_decoder::calc_shmem_size(width, height);
|
||||
|
||||
int fd = memfd_create_compat("cam2magic", 0);
|
||||
if (fd < 0) {
|
||||
ALOGE("memfd_create failed: %s", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ftruncate(fd, size) < 0) {
|
||||
ALOGE("ftruncate failed: %s", strerror(errno));
|
||||
close(fd);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t* base = (uint8_t*)mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
if (base == MAP_FAILED) {
|
||||
ALOGE("mmap failed: %s", strerror(errno));
|
||||
close(fd);
|
||||
return false;
|
||||
}
|
||||
|
||||
memset(base, 0, size);
|
||||
|
||||
auto* header = reinterpret_cast<video_decoder::RingBufferHeader*>(base);
|
||||
header->magic = video_decoder::RING_MAGIC;
|
||||
header->version = video_decoder::RING_VERSION;
|
||||
header->frame_count = video_decoder::RING_FRAME_COUNT;
|
||||
header->frame_width = width;
|
||||
header->frame_height = height;
|
||||
header->frame_size = (uint32_t)(width * height * 3 / 2);
|
||||
header->write_index = 0;
|
||||
header->read_index = 0;
|
||||
header->sequence = 0;
|
||||
header->flags = 0;
|
||||
header->last_timestamp = 0;
|
||||
|
||||
g_decoder.shmem_fd = fd;
|
||||
g_decoder.shmem_base = base;
|
||||
g_decoder.shmem_size = size;
|
||||
g_decoder.header = header;
|
||||
|
||||
ALOGI("Shared memory initialized: fd=%d, size=%zu, %ux%u", fd, size, width, height);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void release_shmem() {
|
||||
if (g_decoder.shmem_base && g_decoder.shmem_base != MAP_FAILED) {
|
||||
munmap(g_decoder.shmem_base, g_decoder.shmem_size);
|
||||
g_decoder.shmem_base = nullptr;
|
||||
}
|
||||
if (g_decoder.shmem_fd >= 0) {
|
||||
close(g_decoder.shmem_fd);
|
||||
g_decoder.shmem_fd = -1;
|
||||
}
|
||||
g_decoder.header = nullptr;
|
||||
g_decoder.shmem_size = 0;
|
||||
}
|
||||
|
||||
bool video_decoder::write_frame_to_ring(const uint8_t* y_data, const uint8_t* u_data,
|
||||
const uint8_t* v_data, int y_stride, int uv_stride,
|
||||
int width, int height) {
|
||||
if (!g_decoder.header || !g_decoder.shmem_base) return false;
|
||||
|
||||
uint32_t frame_size = g_decoder.header->frame_size;
|
||||
uint32_t frame_count = g_decoder.header->frame_count;
|
||||
uint32_t idx = g_decoder.header->write_index % frame_count;
|
||||
uint8_t* dst = get_frame_ptr(g_decoder.shmem_base, idx, frame_size, frame_count);
|
||||
|
||||
int y_plane_size = width * height;
|
||||
int uv_plane_size = (width / 2) * (height / 2);
|
||||
|
||||
uint8_t* dst_y = dst;
|
||||
uint8_t* dst_u = dst + y_plane_size;
|
||||
uint8_t* dst_v = dst + y_plane_size + uv_plane_size;
|
||||
|
||||
for (int row = 0; row < height; row++) {
|
||||
memcpy(dst_y + row * width, y_data + row * y_stride, width);
|
||||
}
|
||||
for (int row = 0; row < height / 2; row++) {
|
||||
memcpy(dst_u + row * (width / 2), u_data + row * uv_stride, width / 2);
|
||||
memcpy(dst_v + row * (width / 2), v_data + row * uv_stride, width / 2);
|
||||
}
|
||||
|
||||
g_decoder.header->write_index = (g_decoder.header->write_index + 1) % (frame_count * 2);
|
||||
g_decoder.header->sequence++;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void* decoder_thread_func(void* arg) {
|
||||
(void)arg;
|
||||
ALOGI("Decoder thread started");
|
||||
|
||||
g_decoder.header->flags |= video_decoder::FLAG_DECODER_RUNNING;
|
||||
|
||||
AMediaExtractor* extractor = AMediaExtractor_new();
|
||||
if (!extractor) {
|
||||
ALOGE("Failed to create MediaExtractor");
|
||||
g_decoder.header->flags &= ~video_decoder::FLAG_DECODER_RUNNING;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int fd = open(g_decoder.video_path.c_str(), O_RDONLY);
|
||||
if (fd < 0) {
|
||||
ALOGE("Failed to open video file: %s (%s)", g_decoder.video_path.c_str(), strerror(errno));
|
||||
AMediaExtractor_delete(extractor);
|
||||
g_decoder.header->flags &= ~video_decoder::FLAG_DECODER_RUNNING;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
media_status_t status = AMediaExtractor_setDataSourceFd(extractor, fd, 0, 0);
|
||||
if (status != AMEDIA_OK) {
|
||||
ALOGE("AMediaExtractor_setDataSourceFd failed: %d", status);
|
||||
close(fd);
|
||||
AMediaExtractor_delete(extractor);
|
||||
g_decoder.header->flags &= ~video_decoder::FLAG_DECODER_RUNNING;
|
||||
return nullptr;
|
||||
}
|
||||
close(fd);
|
||||
|
||||
int video_track = -1;
|
||||
int num_tracks = AMediaExtractor_getTrackCount(extractor);
|
||||
for (int i = 0; i < num_tracks; i++) {
|
||||
AMediaFormat* format = AMediaExtractor_getTrackFormat(extractor, i);
|
||||
if (!format) continue;
|
||||
|
||||
const char* mime = nullptr;
|
||||
if (AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &mime) && mime) {
|
||||
if (strncmp(mime, "video/", 6) == 0) {
|
||||
video_track = i;
|
||||
|
||||
int32_t w = 0, h = 0;
|
||||
AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_WIDTH, &w);
|
||||
AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_HEIGHT, &h);
|
||||
ALOGI("Video track %d: %s, %dx%d", i, mime, w, h);
|
||||
|
||||
if (w > 0 && h > 0) {
|
||||
g_decoder.width = w;
|
||||
g_decoder.height = h;
|
||||
}
|
||||
AMediaFormat_delete(format);
|
||||
break;
|
||||
}
|
||||
}
|
||||
AMediaFormat_delete(format);
|
||||
}
|
||||
|
||||
if (video_track < 0) {
|
||||
ALOGE("No video track found");
|
||||
AMediaExtractor_delete(extractor);
|
||||
g_decoder.header->flags &= ~video_decoder::FLAG_DECODER_RUNNING;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AMediaExtractor_selectTrack(extractor, video_track);
|
||||
AMediaFormat* format = AMediaExtractor_getTrackFormat(extractor, video_track);
|
||||
|
||||
const char* mime = nullptr;
|
||||
AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &mime);
|
||||
if (!mime) {
|
||||
ALOGE("Cannot get MIME type from format");
|
||||
AMediaFormat_delete(format);
|
||||
AMediaExtractor_delete(extractor);
|
||||
g_decoder.header->flags &= ~video_decoder::FLAG_DECODER_RUNNING;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AMediaCodec* codec = AMediaCodec_createDecoderByType(mime);
|
||||
if (!codec) {
|
||||
ALOGE("Failed to create MediaCodec decoder");
|
||||
AMediaFormat_delete(format);
|
||||
AMediaExtractor_delete(extractor);
|
||||
g_decoder.header->flags &= ~video_decoder::FLAG_DECODER_RUNNING;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
status = AMediaCodec_configure(codec, format, nullptr, nullptr, 0);
|
||||
if (status != AMEDIA_OK) {
|
||||
ALOGE("AMediaCodec_configure failed: %d", status);
|
||||
AMediaCodec_delete(codec);
|
||||
AMediaFormat_delete(format);
|
||||
AMediaExtractor_delete(extractor);
|
||||
g_decoder.header->flags &= ~video_decoder::FLAG_DECODER_RUNNING;
|
||||
return nullptr;
|
||||
}
|
||||
AMediaFormat_delete(format);
|
||||
|
||||
status = AMediaCodec_start(codec);
|
||||
if (status != AMEDIA_OK) {
|
||||
ALOGE("AMediaCodec_start failed: %d", status);
|
||||
AMediaCodec_delete(codec);
|
||||
AMediaExtractor_delete(extractor);
|
||||
g_decoder.header->flags &= ~video_decoder::FLAG_DECODER_RUNNING;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ALOGI("Decoder started, feeding frames");
|
||||
|
||||
bool input_done = false;
|
||||
bool output_done = false;
|
||||
int frame_count = 0;
|
||||
const int MAX_FRAMES_PER_LOOP = 100000;
|
||||
|
||||
while (g_decoder.running && !output_done && frame_count < MAX_FRAMES_PER_LOOP) {
|
||||
if (!input_done) {
|
||||
ssize_t buf_idx = AMediaCodec_dequeueInputBuffer(codec, 2000);
|
||||
if (buf_idx >= 0) {
|
||||
size_t buf_size = 0;
|
||||
uint8_t* buf = AMediaCodec_getInputBuffer(codec, buf_idx, &buf_size);
|
||||
if (buf) {
|
||||
ssize_t sample_size = AMediaExtractor_readSampleData(extractor, buf, buf_size);
|
||||
if (sample_size < 0) {
|
||||
ALOGI("Input EOS reached");
|
||||
AMediaCodec_queueInputBuffer(codec, buf_idx, 0, 0, 0,
|
||||
AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM);
|
||||
input_done = true;
|
||||
} else {
|
||||
int64_t pts = AMediaExtractor_getSampleTime(extractor);
|
||||
AMediaCodec_queueInputBuffer(codec, buf_idx, 0, sample_size, pts, 0);
|
||||
AMediaExtractor_advance(extractor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AMediaCodecBufferInfo info;
|
||||
ssize_t out_idx = AMediaCodec_dequeueOutputBuffer(codec, &info, 2000);
|
||||
if (out_idx >= 0) {
|
||||
if (info.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) {
|
||||
ALOGI("Output EOS reached, frame_count=%d", frame_count);
|
||||
output_done = true;
|
||||
}
|
||||
|
||||
size_t out_size = 0;
|
||||
uint8_t* out_buf = AMediaCodec_getOutputBuffer(codec, out_idx, &out_size);
|
||||
if (out_buf && info.size > 0) {
|
||||
int w = g_decoder.width;
|
||||
int h = g_decoder.height;
|
||||
|
||||
if (w > 0 && h > 0 && out_size >= (size_t)(w * h * 3 / 2)) {
|
||||
const uint8_t* y = out_buf;
|
||||
const uint8_t* u = out_buf + w * h;
|
||||
const uint8_t* v = u + (w / 2) * (h / 2);
|
||||
|
||||
video_decoder::write_frame_to_ring(y, u, v, w, w / 2, w, h);
|
||||
frame_count++;
|
||||
|
||||
if (frame_count % 300 == 0) {
|
||||
ALOGI("Decoded %d frames", frame_count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AMediaCodec_releaseOutputBuffer(codec, out_idx, false);
|
||||
} else if (out_idx == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) {
|
||||
AMediaFormat* new_fmt = AMediaCodec_getOutputFormat(codec);
|
||||
int32_t w = 0, h = 0;
|
||||
AMediaFormat_getInt32(new_fmt, AMEDIAFORMAT_KEY_WIDTH, &w);
|
||||
AMediaFormat_getInt32(new_fmt, AMEDIAFORMAT_KEY_HEIGHT, &h);
|
||||
ALOGI("Output format changed: %dx%d", w, h);
|
||||
if (w > 0 && h > 0) {
|
||||
g_decoder.width = w;
|
||||
g_decoder.height = h;
|
||||
}
|
||||
AMediaFormat_delete(new_fmt);
|
||||
}
|
||||
}
|
||||
|
||||
if (g_decoder.loop && !output_done) {
|
||||
ALOGI("Looping video (frame_count=%d)", frame_count);
|
||||
}
|
||||
|
||||
AMediaCodec_stop(codec);
|
||||
AMediaCodec_delete(codec);
|
||||
AMediaExtractor_delete(extractor);
|
||||
|
||||
g_decoder.header->flags &= ~video_decoder::FLAG_DECODER_RUNNING;
|
||||
ALOGI("Decoder thread finished, total frames: %d", frame_count);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int video_decoder::init_decoder(const char* video_path, bool loop) {
|
||||
if (!video_path) return -1;
|
||||
|
||||
g_decoder.video_path = video_path;
|
||||
g_decoder.loop = loop;
|
||||
g_decoder.running = false;
|
||||
g_decoder.width = 0;
|
||||
g_decoder.height = 0;
|
||||
g_decoder.rotation = 0;
|
||||
g_decoder.shmem_fd = -1;
|
||||
g_decoder.shmem_base = nullptr;
|
||||
g_decoder.shmem_size = 0;
|
||||
g_decoder.header = nullptr;
|
||||
|
||||
ALOGI("Decoder initialized for: %s (loop=%d)", video_path, loop);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int video_decoder::start_decoder() {
|
||||
if (g_decoder.running) {
|
||||
ALOGW("Decoder already running");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (g_decoder.width <= 0 || g_decoder.height <= 0) {
|
||||
g_decoder.width = 1920;
|
||||
g_decoder.height = 1080;
|
||||
ALOGW("No video dimensions, defaulting to 1920x1080");
|
||||
}
|
||||
|
||||
if (!init_shmem((uint32_t)g_decoder.width, (uint32_t)g_decoder.height)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
g_decoder.running = true;
|
||||
|
||||
int ret = pthread_create(&g_decoder.decoder_thread, nullptr, decoder_thread_func, nullptr);
|
||||
if (ret != 0) {
|
||||
ALOGE("pthread_create failed: %s", strerror(ret));
|
||||
g_decoder.running = false;
|
||||
release_shmem();
|
||||
return -1;
|
||||
}
|
||||
|
||||
ALOGI("Decoder thread started");
|
||||
return 0;
|
||||
}
|
||||
|
||||
void video_decoder::stop_decoder() {
|
||||
if (!g_decoder.running) return;
|
||||
|
||||
ALOGI("Stopping decoder");
|
||||
g_decoder.running = false;
|
||||
|
||||
if (g_decoder.decoder_thread) {
|
||||
pthread_join(g_decoder.decoder_thread, nullptr);
|
||||
g_decoder.decoder_thread = 0;
|
||||
}
|
||||
|
||||
release_shmem();
|
||||
ALOGI("Decoder stopped");
|
||||
}
|
||||
|
||||
void video_decoder::release_decoder() {
|
||||
stop_decoder();
|
||||
g_decoder.video_path.clear();
|
||||
ALOGI("Decoder released");
|
||||
}
|
||||
|
||||
int video_decoder::get_shmem_fd() {
|
||||
return g_decoder.shmem_fd;
|
||||
}
|
||||
|
||||
int video_decoder::get_decoder_width() {
|
||||
return g_decoder.width;
|
||||
}
|
||||
|
||||
int video_decoder::get_decoder_height() {
|
||||
return g_decoder.height;
|
||||
}
|
||||
|
||||
bool video_decoder::is_decoder_running() {
|
||||
return g_decoder.running && (g_decoder.header &&
|
||||
(g_decoder.header->flags & FLAG_DECODER_RUNNING));
|
||||
}
|
||||
Reference in New Issue
Block a user