406 lines
14 KiB
C++
406 lines
14 KiB
C++
#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;
|
|
}
|
|
|
|
uint8_t* video_decoder::get_shmem_base() {
|
|
return g_decoder.shmem_base;
|
|
}
|
|
|
|
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));
|
|
}
|