Decode Fragmented HEVC Slices using FFMPEG only 1/3 of the frame decoded - ffmpeg

I'm having trouble to decode hevc stream from live 555 rtsp server, for some newer cameras the frame are encoded in different slices. based on the NAL value, it sends I frame then P frame and then unknown frame.
I'm trying to follow the logic from this SO thread, trying to understand the logic.
here's the way I classify the frame type
int CH265Parser::CheckH265IorP(unsigned char* pData, unsigned long dwLen)
{
int naltype = H265GetNALType(pData, dwLen);
int frameType = 0;
switch (naltype)
{
case NAL_VPS:
case NAL_SPS:
case NAL_PPS:
{
break;
}
case NAL_BLA_W_LP:
case NAL_BLA_W_RADL:
case NAL_BLA_N_LP:
case NAL_IDR_W_RADL:
case NAL_IDR_N_LP:
case NAL_CRA_NUT:
{
// I-Frame
frameType = 1;
break;
}
case NAL_TRAIL_N:
case NAL_TRAIL_R:
case NAL_TSA_N:
case NAL_TSA_R:
case NAL_STSA_N:
case NAL_STSA_R:
case NAL_RADL_N:
case NAL_RADL_R:
case NAL_RASL_N:
case NAL_RASL_R:
{
// P-Frame
frameType = 2;
break;
}
case NAL_AUD:
case NAL_SEI_SUFFIX:
case NAL_SEI_PREFIX:
{
break;
}
default:
break;
}
return frameType;
}
and here's how I get the NAL
int CH265Parser::H265GetNALType(void* pBSBuf, long nBSLen)
{
if (nBSLen < 5)
return -1;
int pos = FindNALBegin((unsigned char*)pBSBuf, nBSLen);
unsigned char* pBS = (unsigned char*)pBSBuf;
unsigned long nType = (pBS[pos] >> 1) & 0x3F;
if (nType >= NAL_TRAIL_N && nType <= NAL_SEI_SUFFIX)
return nType;
return -1;
}
long CH265Parser::FindNALBegin(unsigned char* pszBuffer, long nLength)
{
for (int i = 0; i < nLength - 4; i++)
{
if (pszBuffer[i] == 0x00 && pszBuffer[i + 1] == 0x00)
{
if (pszBuffer[i + 2] == 0x01)
return i + 3;
else if (pszBuffer[i + 2] == 0x00 && pszBuffer[i + 3] == 0x01)
return i + 4;
}
}
return -1;
}
now based on the information, here's my decoder looks like (with additional info header to the buffer):
auto headSize = sizeof(isap::media::AV_HEADER);
auto header = reinterpret_cast<const isap::media::AV_HEADER*>(packets_.data());
std::string frameType = "I";
if (header->nFrameType == isap::media::frame_type::_FRAME_TYPE_P)
frameType = "P";
else if (header->nFrameType == isap::media::frame_type::_FRAME_TYPE_UNKNOWN)
frameType = "B";
printf("frame %s\n", frameType.c_str());
if (frameType == "P" || frameType == "B") {
packets_ = packets_ + packet;
}
else if (frameType == "I") {
packets_ = packet;
}
auto ec = decoder_.send(reinterpret_cast<const uint8_t*>(packets_.data()), static_cast<int32_t>(packets_.size()));
if (ec) {
error_ = "media_client::decode_video send failed: ";
kt::error_message_to(std::back_inserter(error_), ec);
// throw stop_signal();
}
else {
packets_ = "";
for (;;) {
auto frame = av_frame_alloc();
ec = decoder_.receive(frame);
if (ec) {
av_frame_free(&frame);
if (ec.value() == AVERROR(EAGAIN)) break;
error_ = "media_client::decode_video receive failed: ";
kt::error_message_to(std::back_inserter(error_), ec);
throw stop_signal();
}
push_frame_queue(frame);
}
}
end result
output
please note, the code above works fine for hevc with lower resolution or non fragmented stream, and the decoder itself works fine for h264 stream. did i concatenate the slice correctly? I'm very new to this subject.
output for hevc fragmented stream
frame I
frame I
[hevc # 0000025A9CD08400] PPS changed between slices.
[hevc # 0000025A9CD08400] Error parsing NAL unit #3.
frame I
[hevc # 0000025A9CD08400] PPS changed between slices.
[hevc # 0000025A9CD08400] Error parsing NAL unit #3.
frame I
frame B
frame P
[hevc # 0000025A9CD08400] First slice in a frame missing.
frame P
[hevc # 0000025A9CD08400] First slice in a frame missing.
width: 1920, height: 1080, size: 8294400 // this only returns 1/3 of the frame successfully decoded
output for hevc non fragmented stream
frame I
frame I
[hevc # 00000185A06EBA40] PPS id out of range: 0
[hevc # 00000185A06EBA40] Error parsing NAL unit #0.
frame P
[hevc # 00000185A06EBA40] PPS id out of range: 0
[hevc # 00000185A06EBA40] Error parsing NAL unit #0.
width: 1920, height: 1080, size: 8294400
frame P
h264 output, no error / warning
frame I
frame I
frame P
width: 1280, height: 1280, size: 6553600
frame P
width: 1280, height: 1280, size: 6553600
frame P
width: 1280, height: 1280, size: 6553600
frame P
width: 1280, height: 1280, size: 6553600
any help would be appreciated.

Related

Ffmpeg push video is successful, but why is there a mistake at the beginning?

I'm a beginner of ffmpeg. Now I use ffmpeg to push video stream to RTMP server, which can push successfully. But at the beginning, there were some errors. I don't know what caused them. This error happens every time. My read_ packet() callback function, the read data is READ_BUF_LEN = 1024 * 12 = 12288 bytes, it seems that it can't find the key frame in 12288 bytes, because READ_BUF_LEN is too small?
#include "/usr/local/include/libavcodec/avcodec.h"
#include "/usr/local/include/libavformat/avformat.h"
#include "/usr/local/include/libavfilter/avfilter.h"
#include "/usr/local/include/libavutil/mathematics.h"
#include "/usr/local/include/libavutil/time.h"
extern VideoDataStruct *VideoDataListHeader;
extern PushVideoStruct PushVideoInfo;
extern enum IsPushingVideo IsPushingVideoFlag;
extern UCHAR ChangeAnotherVideo;
typedef long long int64;
#define READ_BUF_LEN 1024*12
extern enum IsStopPushVideo StopPushVideoFlag;
static int read_packet(void *opaque, uint8_t *buf, int buf_size)
{
int64 dataLen = 0;
while (dataLen < buf_size)
{
if ((VideoDataListHeader != NULL) && (VideoDataListHeader->flag == 1))
{
memcpy(&buf[dataLen], VideoDataListHeader->buf, sizeof(VideoDataListHeader->buf));
dataLen += sizeof(VideoDataListHeader->buf);
VideoDataListHeader->flag = 0;
VideoDataListHeader = VideoDataListHeader->next;
}
else
{
usleep(10000);
}
}
return buf_size;
}
void *PushVideoFunction(void *arg)
{
AVFormatContext *m_pFmtCtx = NULL;
AVPacket pkt;
AVIOContext *m_pIOCtx = NULL;
AVInputFormat *in_fmt = NULL;
int ret = 0;
unsigned int i = 0;
int vid_idx =-1;
unsigned char *m_pIOBuf = NULL;
int m_pIOBuf_size = READ_BUF_LEN;
int64 start_time = 0;
int frame_index = 0;
//const char *rtmp_url = "rtmp://192.168.1.108/mytv/01";
char rtmp_url[140] = {0};
memset(rtmp_url, 0, sizeof(rtmp_url));
strcpy(rtmp_url, PushVideoInfo.VideoServer);
CHAR fileName[64] = {0};
avformat_network_init();
if (strcmp(PushVideoInfo.VideoType, REAL_VIDEO) == 0)
{
m_pIOBuf = (unsigned char*)av_malloc(m_pIOBuf_size);
if(m_pIOBuf == NULL)
{
printf("av malloc failed!\n");
goto end;
}
m_pIOCtx = avio_alloc_context(m_pIOBuf, m_pIOBuf_size, 0, NULL, read_packet, NULL, NULL);
if (!m_pIOCtx)
{
printf("avio alloc context failed!\n");
goto end;
}
m_pFmtCtx = avformat_alloc_context();
if (!m_pFmtCtx)
{
printf("avformat alloc context failed!\n");
goto end;
}
//m_pFmtCtx->probesize = BYTES_PER_FRAME * 8;
m_pFmtCtx->pb = m_pIOCtx;
ret = avformat_open_input(&m_pFmtCtx, "", in_fmt, NULL);
}
else if (strcmp(PushVideoInfo.VideoType, HISTORY_VIDEO) == 0)
{
sprintf(fileName, "%s", VIDEO_FILE_FOLDER);
sprintf(fileName+strlen(fileName), "%s", PushVideoInfo.VideoFile);
ret = avformat_open_input(&m_pFmtCtx, fileName, NULL, NULL);
}
if (ret < 0)
{
printf("avformat open failed!\n");
goto end;
}
ret = avformat_find_stream_info(m_pFmtCtx, 0);
if (ret < 0)
{
printf("could not find stream info!\n");
goto end;
}
for(i = 0; i < m_pFmtCtx->nb_streams; i++)
{
if((m_pFmtCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) && (vid_idx < 0))
{
vid_idx = i;
}
}
AVFormatContext *octx = NULL;
ret = avformat_alloc_output_context2(&octx, 0, "flv", rtmp_url);
if (ret < 0)
{
printf("avformat alloc output context2 failed!\n");
goto end;
}
av_init_packet(&pkt);
for (i = 0;i < m_pFmtCtx->nb_streams; i++)
{
AVCodec *codec = avcodec_find_decoder(m_pFmtCtx->streams[i]->codecpar->codec_id);
AVStream *out = avformat_new_stream(octx, codec);
ret = avcodec_parameters_copy(out->codecpar, m_pFmtCtx->streams[i]->codecpar);
out->codecpar->codec_tag = 0;
}
ret = avio_open(&octx->pb, rtmp_url, AVIO_FLAG_WRITE);
if (!octx->pb)
{
printf("avio open failed!\n");
goto end;
}
ret = avformat_write_header(octx, 0);
if (ret < 0)
{
printf("avformat write header failed!\n");
goto end;
}
start_time = av_gettime();
AVStream *in_stream, *out_stream;
AVRational time_base1;
AVRational time_base;
AVRational time_base_q;
int64 calc_duration;
int64 pts_time;
int64 now_time;
ChangeAnotherVideo = 0;
while((!StopPushVideoFlag) && (ChangeAnotherVideo == 0))
{
ret = av_read_frame(m_pFmtCtx, &pkt);
if (ret < 0)
{
break;
}
if (pkt.pts == AV_NOPTS_VALUE)
{
time_base1 = m_pFmtCtx->streams[vid_idx]->time_base;
calc_duration = (double)AV_TIME_BASE/av_q2d(m_pFmtCtx->streams[vid_idx]->r_frame_rate);
pkt.pts = (double)(frame_index*calc_duration)/(double)(av_q2d(time_base1)*AV_TIME_BASE);
pkt.dts = pkt.pts;
pkt.duration = (double)calc_duration/(double)(av_q2d(time_base1)*AV_TIME_BASE);
}
if (pkt.stream_index == vid_idx)
{
time_base = m_pFmtCtx->streams[vid_idx]->time_base;
time_base_q = (AVRational){1, AV_TIME_BASE};
pts_time = av_rescale_q(pkt.dts, time_base, time_base_q);
now_time = av_gettime() - start_time;
if (pts_time > now_time)
{
av_usleep(pts_time - now_time);
}
}
in_stream = m_pFmtCtx->streams[pkt.stream_index];
out_stream = octx->streams[pkt.stream_index];
pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, (enum AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, (enum AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
pkt.pos = -1;
if(pkt.stream_index == vid_idx)
{
printf("Send %8d video frames to output URL\n",frame_index);
frame_index++;
}
ret = av_interleaved_write_frame(octx, &pkt);
if (ret < 0)
{
goto end;
}
av_packet_unref(&pkt);
}
end:
printf("---------------------------------stop push video -------------------------------------------\n");
StopPushVideoFlag = NO_STOP_PUSH;
IsPushingVideoFlag = NO_PUSHING;
ChangeAnotherVideo = 0;
avformat_close_input(&m_pFmtCtx);
if (octx)
{
avio_closep(&octx->pb);
avformat_free_context(octx);
}
/* note: the internal buffer could have changed, and be != avio_ctx_buffer */
if (m_pIOCtx)
{
av_freep(&m_pIOCtx->buffer);
av_freep(&m_pIOCtx);
}
if (ret < 0)
{
printf("Error occured : %s\n", av_err2str(ret));
//return 1;
}
pthread_exit((void*)"push video end!");
}
void PushVideo(void)
{
int ret = 0;
pthread_t pushVideoThread;
ret = pthread_create(&pushVideoThread, NULL, PushVideoFunction, NULL);
if(ret != 0)
{
printf("error : push video thread create failed!\n");
exit(-1);
}
else
{
printf("(debug) push video thread create success!\n");
}
}
push error:
[h264 # 0xffff54001480] non-existing PPS 0 referenced
[h264 # 0xffff54001480] non-existing PPS 0 referenced
[h264 # 0xffff54001480] decode_slice_header error
[h264 # 0xffff54001480] no frame!
[h264 # 0xffff54001480] non-existing PPS 0 referenced
[h264 # 0xffff54001480] non-existing PPS 0 referenced
[h264 # 0xffff54001480] decode_slice_header error
[h264 # 0xffff54001480] no frame!
[h264 # 0xffff54001480] non-existing PPS 0 referenced
[h264 # 0xffff54001480] non-existing PPS 0 referenced
[h264 # 0xffff54001480] decode_slice_header error
[h264 # 0xffff54001480] no frame!
[h264 # 0xffff54001480] non-existing PPS 0 referenced
[h264 # 0xffff54001480] non-existing PPS 0 referenced
[h264 # 0xffff54001480] decode_slice_header error
[h264 # 0xffff54001480] no frame!
[h264 # 0xffff54001480] non-existing PPS 0 referenced
[h264 # 0xffff54001480] non-existing PPS 0 referenced
[h264 # 0xffff54001480] decode_slice_header error
[h264 # 0xffff54001480] no frame!
[h264 # 0xffff54001480] non-existing PPS 0 referenced
[h264 # 0xffff54001480] non-existing PPS 0 referenced
[h264 # 0xffff54001480] decode_slice_header error
[h264 # 0xffff54001480] no frame!
[h264 # 0xffff54001480] non-existing PPS 0 referenced
[h264 # 0xffff54001480] non-existing PPS 0 referenced
[h264 # 0xffff54001480] decode_slice_header error
[h264 # 0xffff54001480] no frame!
[h264 # 0xffff54001480] non-existing PPS 0 referenced
[h264 # 0xffff54001480] non-existing PPS 0 referenced
[h264 # 0xffff54001480] decode_slice_header error
[h264 # 0xffff54001480] no frame!
[h264 # 0xffff54001480] non-existing PPS 0 referenced
[h264 # 0xffff54001480] non-existing PPS 0 referenced
[h264 # 0xffff54001480] decode_slice_header error
[h264 # 0xffff54001480] corrupted macroblock 11 13 (total_coeff=-1)
[h264 # 0xffff54001480] error while decoding MB 11 13
[h264 # 0xffff54001480] concealing 78 DC, 78 AC, 78 MV errors in I frame
[h264 # 0xffff54001480] mb_type 84 in P slice too large at 16 6
[h264 # 0xffff54001480] error while decoding MB 16 6
[h264 # 0xffff54001480] concealing 213 DC, 213 AC, 213 MV errors in P frame
As #Gyan said, because when I cache video data, I don't cache it with SPS, PPS, I frame, so these errors will be reported when the avformat_find_stream_info function is executed.
Although these errors will not affect the subsequent streaming process, in order to reduce the display of these errors, I found the SPS & PPS & I frame before caching data.

AVFoundation: how to change frame rate

I want to lower the frame rate of my AVCaptureSession, since my video processing cannot cope with 30fps currently. I found similiar quesions already, yet the proposed answers have not worked for me so far. I am using the built in macBook camera by the way.
I tried:
if ([_camera lockForConfiguration:nil])
{
_camera.activeVideoMinFrameDuration = CMTimeMake(1, 20);
_camera.activeVideoMaxFrameDuration = CMTimeMake(1, 20);
[_camera unlockForConfiguration];
}
but this won't compile and throws a Thread1: signal SIGABRT error on the third line. I suspect this is because the device may not support 20 fps.
The other try from the forums was this:
- (void)attemptToConfigureFPS
{
NSError *error;
if (![_camera lockForConfiguration:&error]) {
NSLog(#"Could not lock device %# for configuration: %#", self, error);
return;
}
AVCaptureDeviceFormat *format = _camera.activeFormat;
double epsilon = 0.00000001;
int desiredFrameRate = 10;
for (AVFrameRateRange *range in format.videoSupportedFrameRateRanges) {
NSLog(#"Pre Minimum frame rate: %f Max = %f", range.minFrameRate, range.maxFrameRate);
if (range.minFrameRate <= (desiredFrameRate + epsilon) &&
range.maxFrameRate >= (desiredFrameRate - epsilon)) {
NSLog(#"Setting Frame Rate.");
_camera.activeVideoMaxFrameDuration = (CMTime){
.value = 1,
.timescale = desiredFrameRate,
.flags = kCMTimeFlags_Valid,
.epoch = 0,
};
_camera.activeVideoMinFrameDuration = (CMTime){
.value = 1,
.timescale = desiredFrameRate,
.flags = kCMTimeFlags_Valid,
.epoch = 0,
};
self.activeVideoMinFrameDuration = self.activeVideoMaxFrameDuration;
NSLog(#"Post Minimum frame rate: %f Max = %f", range.minFrameRate, range.maxFrameRate);
break;
}
}
[_camera unlockForConfiguration];
// Audit the changes
for (AVFrameRateRange *range in format.videoSupportedFrameRateRanges) {
NSLog(#"Post Minimum frame rate: %f Max = %f", range.minFrameRate, range.maxFrameRate);
}
}
This time the code compiles, but examination with the debugger reveals, that
if (range.minFrameRate <= (desiredFrameRate + epsilon) &&
range.maxFrameRate >= (desiredFrameRate - epsilon))
evaluates to NO, so the rest of the code, where the framerate should be adjusted, is never executed. This seems odd to me, and help of yours is greatly appreciated. Thank you very much in advance!

How to read any frame while having frame number using ffmpeg av_seek_frame()

int64_t timeBase;
timeBase = (int64_t(pavStrm-> time_base.num) * AV_TIME_BASE) / int64_t(pavStrm->time_base.den);
int64_t seekTarget = int64_t(iFrameNumber) * timeBase;
av_seek_frame(fmt_ctx, -1, seekTarget, AVSEEK_FLAG_FRAME);
here I want to read next 5 frame after iFrameNumebr
for(int iCnt = 0; iCnt <= 4; iCnt++)
{
iRet = av_read_frame(fmt_ctx, &pkt);
do
{
ret = decode_packet(&got_frame, 0);
if (ret < 0)
break;
pkt.data += ret;
pkt.size -= ret;
}while (pkt.size > 0);
av_free_packet(&pkt);
}
static int decode_packet(int *got_frame, int cached)
{
int ret = 0;
int decoded = pkt.size;
*got_frame = 0;
if (pkt.stream_index == video_stream_idx)
{
/* decode video frame */
ret = avcodec_decode_video2(video_dec_ctx, frame, got_frame, &pkt);
}
when i am using AVSEEK_FLAG_BACKWARD its return 5 packet and 5 frame first two is blank but correct.
when i am using AVSEEK_FLAG_FRAME its return 5 packet and 3 frame which are not first 3 frame its return specific frame from video.
for any iFrameNumber
so please help me how to get frame while having frame number and what is exact value of seektarget 3rd param of av_seek_frame()
also I have problem while converting frame to rgb24 format
I think av_seek_frame() is one of the most common but difficult to understand function, also not well commented enough.
If the flag AVSEEK_FLAG_FRAME is set, the third parameter should be a frame number you want to seek, which you're doing fine.
Let's see a example to have a better understand of av_seek_frame():
Say I have a video of 10 frames, with fps=10. The first and fifth frame is key frame (I Frame or intra frame). Others are P frames or even B frames in some format.
0 1 2 3 4 5 6 7 8 9 (frame number)
0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 (timebase)
av_seek_frame(fmt_ctx, -1, 2, AVSEEK_FLAG_FRAME);
av_seek_frame(fmt_ctx, -1, 0.15, 0);
// These will seek to the fifth frame. Cause `AVSEEK_FLAG_ANY` is not given. Seeking to the next key frame after third parameter.
av_seek_frame(fmt_ctx, -1, 2, AVSEEK_FLAG_FRAME | AVSEEK_FLAG_ANY);
// This will seek to exactly the third parameter specified. But probably only a frame with no actual meaning. (We can't get a meaningful image if no related I/P/B frames given.)
av_seek_frame(fmt_ctx, -1, 0.15, AVSEEK_FLAG_ANY);
// Seek to 0.2. Nothing interesting as above.
av_seek_frame(fmt_ctx, -1, 0.15, AVSEEK_FLAG_ANY | AVSEEK_FLAG_BACKWARD);
// Seek to 0.1. Also nothing interesting.
av_seek_frame(fmt_ctx, -1, 2, AVSEEK_FLAG_FRAME | AVSEEK_FLAG_BACKWARD);
// Got the first frame. Seeking to the nearest key frame before the third parameter.
So if I'd like to get arbitrary frame, usually seeking with AVSEEK_FLAG_BACKWARD first, decoding as usual. Then check the first several packets pts and duration, see if we need to drop them.
int64_t FrameToPts(AVStream* pavStream, int frame) const
{
return (int64_t(frame) * pavStream->r_frame_rate.den * pavStream-
>time_base.den) /
(int64_t(pavStream->r_frame_rate.num) *
pavStream->time_base.num);
}
iSeekTarget = FrameToPts(m_pAVVideoStream, max(0, lFrame));
iSuccess = av_seek_frame(m_pAVFmtCtx, m_iVideo_Stream_idx,
iSeekTarget, iSeekFlag);
AVPacket avPacket;
iRet = av_read_frame(m_pAVFmtCtx, &avPacket);
timeBase = (int64_t(video_stream-> time_base.num) * AV_TIME_BASE) / int64_t(video_stream->time_base.den);
int64_t seekTarget = int64_t(iFrameNumber) * timeBase * (video_stream->time_base.den / video_stream->avg_frame_rate.num);
int iiiret = av_seek_frame(fmt_ctx, -1, seekTarget, AVSEEK_FLAG_FRAME);

Decoding h264 stream from gstreamer on iOS

I have iOS app which can receive and decode annex b h264 stream
I have a device with camera which streams h264 with gstreamer in AVC format
Can I make gstreamer stream in annex b format ?
OR
How do I extract the sequence header (or the sep file) from gstreamer ?
Annex-B format is simply NAL unit with exactly the 4 byte magic sequence begining a NAL unit: 0x00, 0x00, 0x00, 0x01.
Remember H.264 NAL units can be of 3 of 4 bytes for their magic sequence. If you write your own parser you can handle this by doing something like:
size_t lastNaluStartingOffset = frameSize + 1;
for (size_t i = 0; i < frameSize - 10; ++i) {
if (frameBuffer[i] == ZERO_BYTE && frameBuffer[i+1] == ZERO_BYTE && frameBuffer[i+2] == MAGIC_BYTE) {
uint32_t naluType = (frameBuffer[i+3] & 0x1F);
NaluSegment *segment = [[NaluSegment alloc] initWithType:naluType atOffset:i withHeaderSize:3];
if (i > 0 && frameBuffer[i-1] == ZERO_BYTE) {
// its actally a 4 byte code!
[segment setOffset:[segment offset]-1];
[segment setHeaderSize:4];
}
....
Now you are free to format your NAL unit as you like.

Lua, Odd behavior with bitmap reading

In a bitmap reader I found and modified, the first few colors of some images are innacurate by a large amount, in other images it reads perfectly, in just now, the first 7 or so colors of an image that I must have it read are not accurate at all. I don't understand byte orders, so please help!
Heres my modified copy of the code:
---
-- (Evil Steve)Because I'm a kind and wonderful person: http://www.gamedev.net/topic/572784-lua-read-bitmap/
---
function error(err)
-- Replace with your own error output method:
print(err);
end
-- Helper function: Parse a 16-bit WORD from the binary string
function ReadWORD(str, offset)
local loByte = str:byte(offset);
local hiByte = str:byte(offset+1);
return hiByte*256 + loByte;
end
-- Helper function: Parse a 32-bit DWORD from the binary string
function ReadDWORD(str, offset)
local loWord = ReadWORD(str, offset);
local hiWord = ReadWORD(str, offset+2);
return hiWord*65536 + loWord;
end
-- Process a bitmap file in a string, and call DrawPoint for each pixel
function OpenBitmap(File, Stream)
if Stream == nil then Stream = false end
local bytecode = File:read("*a")
-------------------------
-- Parse BITMAPFILEHEADER
-------------------------
local offset = 1;
local bfType = ReadWORD(bytecode, offset);
if(bfType ~= 0x4D42) then
error("Not a bitmap file (Invalid BMP magic value)");
return;
end
local bfOffBits = ReadWORD(bytecode, offset+10);
-------------------------
-- Parse BITMAPINFOHEADER
-------------------------
offset = 15; -- BITMAPFILEHEADER is 14 bytes long
local biWidth = ReadDWORD(bytecode, offset+4);
local biHeight = ReadDWORD(bytecode, offset+8);
local biBitCount = ReadWORD(bytecode, offset+14);
local biCompression = ReadDWORD(bytecode, offset+16);
if(biBitCount ~= 24) then
error("Only 24-bit bitmaps supported (Is " .. biBitCount .. "bpp)");
return;
end
if(biCompression ~= 0) then
error("Only uncompressed bitmaps supported (Compression type is " .. biCompression .. ")");
return;
end
---------------------
-- Parse bitmap image
---------------------
local TmpImg = {}
if Stream == false then
for y = biHeight-1, 0, -1 do
offset = bfOffBits + (biWidth*biBitCount/8)*y + 1;
for x = 0, biWidth-1 do
local b = bytecode:byte(offset);
local g = bytecode:byte(offset+1);
local r = bytecode:byte(offset+2);
offset = offset + 3;
TmpImg[#TmpImg+1] = {r,g,b}
end
end
else
for y = biHeight-1, 0, -1 do
offset = bfOffBits + (biWidth*biBitCount/8)*y + 1;
for x = 0, biWidth-1 do
local b = bytecode:byte(offset);
local g = bytecode:byte(offset+1);
local r = bytecode:byte(offset+2);
offset = offset + 3;
TmpImg[#TmpImg+1] = r
TmpImg[#TmpImg+1] = g
TmpImg[#TmpImg+1] = b
end
end
end
return TmpImg, biWidth, biHeight
end
function OpenBmp(FileName, Stream)
if Stream == nil then Stream = false end
if FileName == nil then
return false
end
local File = assert(io.open(FileName, 'rb'))
local Data, Width, Height = OpenBitmap(File, Stream)
File:close()
return Data, Width, Height
end
I cannot give you the code I run with this, sadly, because it has too many dependencies to bother with, but its output is:
<254, 254, 254, 256>
<99, 254, 254, 256>
<49, 74, 91, 256>
When ran with the following bmp colors:
<90, 106, 113, 256>
<188, 194, 197, 256>
<254, 254, 254, 256>
I don't see any pattern, and the bmp reader seems to make sense, it prints no errors when reading, and I made sure to save the bmp as 24 bit as required. all help appreciated :-)
In the example above, offset wasn't considering that row widths must always be a multiple of 4 bytes wide, with it padded if it was below the multiple of 4. You can resolve this by rounding up row width to the nearest multiple of 4, which explains why the function sometimes read images accurately, and other times not.
the behavior from the pixels in the beginning being false, but the rest later on being accurate was due to creeping, logically, the first would be accurate, with the last inaccurate, but the creeping went the opposite way due to bitmaps being read bottom-up and right-left.

Resources