Loading media/java/android/media/MediaCodec.java +90 −41 Original line number Diff line number Diff line Loading @@ -45,6 +45,7 @@ import java.nio.ByteOrder; import java.nio.ReadOnlyBufferException; import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; Loading Loading @@ -1803,7 +1804,7 @@ final public class MediaCodec { synchronized(mBufferLock) { switch (mBufferMode) { case BUFFER_MODE_LEGACY: validateInputByteBuffer(mCachedInputBuffers, index); validateInputByteBufferLocked(mCachedInputBuffers, index); break; case BUFFER_MODE_BLOCK: while (mQueueRequests.size() <= index) { Loading Loading @@ -1832,7 +1833,7 @@ final public class MediaCodec { synchronized(mBufferLock) { switch (mBufferMode) { case BUFFER_MODE_LEGACY: validateOutputByteBuffer(mCachedOutputBuffers, index, info); validateOutputByteBufferLocked(mCachedOutputBuffers, index, info); break; case BUFFER_MODE_BLOCK: while (mOutputFrames.size() <= index) { Loading Loading @@ -2320,10 +2321,6 @@ final public class MediaCodec { */ public final void start() { native_start(); synchronized(mBufferLock) { cacheBuffers(true /* input */); cacheBuffers(false /* input */); } } private native final void native_start(); Loading Loading @@ -2380,8 +2377,10 @@ final public class MediaCodec { */ public final void flush() { synchronized(mBufferLock) { invalidateByteBuffers(mCachedInputBuffers); invalidateByteBuffers(mCachedOutputBuffers); invalidateByteBuffersLocked(mCachedInputBuffers); invalidateByteBuffersLocked(mCachedOutputBuffers); mValidInputIndices.clear(); mValidOutputIndices.clear(); mDequeuedInputBuffers.clear(); mDequeuedOutputBuffers.clear(); } Loading Loading @@ -2665,14 +2664,14 @@ final public class MediaCodec { + "is not compatible with CONFIGURE_FLAG_USE_BLOCK_MODEL. " + "Please use getQueueRequest() to queue buffers"); } invalidateByteBuffer(mCachedInputBuffers, index); invalidateByteBufferLocked(mCachedInputBuffers, index, true /* input */); mDequeuedInputBuffers.remove(index); } try { native_queueInputBuffer( index, offset, size, presentationTimeUs, flags); } catch (CryptoException | IllegalStateException e) { revalidateByteBuffer(mCachedInputBuffers, index); revalidateByteBuffer(mCachedInputBuffers, index, true /* input */); throw e; } } Loading Loading @@ -2935,14 +2934,14 @@ final public class MediaCodec { + "is not compatible with CONFIGURE_FLAG_USE_BLOCK_MODEL. " + "Please use getQueueRequest() to queue buffers"); } invalidateByteBuffer(mCachedInputBuffers, index); invalidateByteBufferLocked(mCachedInputBuffers, index, true /* input */); mDequeuedInputBuffers.remove(index); } try { native_queueSecureInputBuffer( index, offset, info, presentationTimeUs, flags); } catch (CryptoException | IllegalStateException e) { revalidateByteBuffer(mCachedInputBuffers, index); revalidateByteBuffer(mCachedInputBuffers, index, true /* input */); throw e; } } Loading Loading @@ -2976,7 +2975,7 @@ final public class MediaCodec { int res = native_dequeueInputBuffer(timeoutUs); if (res >= 0) { synchronized(mBufferLock) { validateInputByteBuffer(mCachedInputBuffers, res); validateInputByteBufferLocked(mCachedInputBuffers, res); } } return res; Loading Loading @@ -3573,10 +3572,10 @@ final public class MediaCodec { int res = native_dequeueOutputBuffer(info, timeoutUs); synchronized (mBufferLock) { if (res == INFO_OUTPUT_BUFFERS_CHANGED) { cacheBuffers(false /* input */); cacheBuffersLocked(false /* input */); } else if (res >= 0) { validateOutputByteBuffer(mCachedOutputBuffers, res, info); if (mHasSurface) { validateOutputByteBufferLocked(mCachedOutputBuffers, res, info); if (mHasSurface || mCachedOutputBuffers == null) { mDequeuedOutputInfos.put(res, info.dup()); } } Loading Loading @@ -3670,9 +3669,9 @@ final public class MediaCodec { synchronized(mBufferLock) { switch (mBufferMode) { case BUFFER_MODE_LEGACY: invalidateByteBuffer(mCachedOutputBuffers, index); invalidateByteBufferLocked(mCachedOutputBuffers, index, false /* input */); mDequeuedOutputBuffers.remove(index); if (mHasSurface) { if (mHasSurface || mCachedOutputBuffers == null) { info = mDequeuedOutputInfos.remove(index); } break; Loading Loading @@ -3824,15 +3823,24 @@ final public class MediaCodec { private ByteBuffer[] mCachedInputBuffers; private ByteBuffer[] mCachedOutputBuffers; private BitSet mValidInputIndices = new BitSet(); private BitSet mValidOutputIndices = new BitSet(); private final BufferMap mDequeuedInputBuffers = new BufferMap(); private final BufferMap mDequeuedOutputBuffers = new BufferMap(); private final Map<Integer, BufferInfo> mDequeuedOutputInfos = new HashMap<Integer, BufferInfo>(); final private Object mBufferLock; private final void invalidateByteBuffer( @Nullable ByteBuffer[] buffers, int index) { if (buffers != null && index >= 0 && index < buffers.length) { private void invalidateByteBufferLocked( @Nullable ByteBuffer[] buffers, int index, boolean input) { if (buffers == null) { if (index < 0) { throw new IllegalStateException("index is negative (" + index + ")"); } BitSet indices = input ? mValidInputIndices : mValidOutputIndices; indices.clear(index); } else if (index >= 0 && index < buffers.length) { ByteBuffer buffer = buffers[index]; if (buffer != null) { buffer.setAccessible(false); Loading @@ -3840,9 +3848,14 @@ final public class MediaCodec { } } private final void validateInputByteBuffer( private void validateInputByteBufferLocked( @Nullable ByteBuffer[] buffers, int index) { if (buffers != null && index >= 0 && index < buffers.length) { if (buffers == null) { if (index < 0) { throw new IllegalStateException("index is negative (" + index + ")"); } mValidInputIndices.set(index); } else if (index >= 0 && index < buffers.length) { ByteBuffer buffer = buffers[index]; if (buffer != null) { buffer.setAccessible(true); Loading @@ -3851,10 +3864,16 @@ final public class MediaCodec { } } private final void revalidateByteBuffer( @Nullable ByteBuffer[] buffers, int index) { private void revalidateByteBuffer( @Nullable ByteBuffer[] buffers, int index, boolean input) { synchronized(mBufferLock) { if (buffers != null && index >= 0 && index < buffers.length) { if (buffers == null) { if (index < 0) { throw new IllegalStateException("index is negative (" + index + ")"); } BitSet indices = input ? mValidInputIndices : mValidOutputIndices; indices.set(index); } else if (index >= 0 && index < buffers.length) { ByteBuffer buffer = buffers[index]; if (buffer != null) { buffer.setAccessible(true); Loading @@ -3863,9 +3882,14 @@ final public class MediaCodec { } } private final void validateOutputByteBuffer( private void validateOutputByteBufferLocked( @Nullable ByteBuffer[] buffers, int index, @NonNull BufferInfo info) { if (buffers != null && index >= 0 && index < buffers.length) { if (buffers == null) { if (index < 0) { throw new IllegalStateException("index is negative (" + index + ")"); } mValidOutputIndices.set(index); } else if (index >= 0 && index < buffers.length) { ByteBuffer buffer = buffers[index]; if (buffer != null) { buffer.setAccessible(true); Loading @@ -3874,7 +3898,7 @@ final public class MediaCodec { } } private final void invalidateByteBuffers(@Nullable ByteBuffer[] buffers) { private void invalidateByteBuffersLocked(@Nullable ByteBuffer[] buffers) { if (buffers != null) { for (ByteBuffer buffer: buffers) { if (buffer != null) { Loading @@ -3884,27 +3908,29 @@ final public class MediaCodec { } } private final void freeByteBuffer(@Nullable ByteBuffer buffer) { private void freeByteBufferLocked(@Nullable ByteBuffer buffer) { if (buffer != null /* && buffer.isDirect() */) { // all of our ByteBuffers are direct java.nio.NioUtils.freeDirectBuffer(buffer); } } private final void freeByteBuffers(@Nullable ByteBuffer[] buffers) { private void freeByteBuffersLocked(@Nullable ByteBuffer[] buffers) { if (buffers != null) { for (ByteBuffer buffer: buffers) { freeByteBuffer(buffer); freeByteBufferLocked(buffer); } } } private final void freeAllTrackedBuffers() { private void freeAllTrackedBuffers() { synchronized(mBufferLock) { freeByteBuffers(mCachedInputBuffers); freeByteBuffers(mCachedOutputBuffers); freeByteBuffersLocked(mCachedInputBuffers); freeByteBuffersLocked(mCachedOutputBuffers); mCachedInputBuffers = null; mCachedOutputBuffers = null; mValidInputIndices.clear(); mValidOutputIndices.clear(); mDequeuedInputBuffers.clear(); mDequeuedOutputBuffers.clear(); mQueueRequests.clear(); Loading @@ -3912,14 +3938,31 @@ final public class MediaCodec { } } private final void cacheBuffers(boolean input) { private void cacheBuffersLocked(boolean input) { ByteBuffer[] buffers = null; try { buffers = getBuffers(input); invalidateByteBuffers(buffers); invalidateByteBuffersLocked(buffers); } catch (IllegalStateException e) { // we don't get buffers in async mode } if (buffers != null) { BitSet indices = input ? mValidInputIndices : mValidOutputIndices; for (int i = 0; i < buffers.length; ++i) { ByteBuffer buffer = buffers[i]; if (buffer == null || !indices.get(i)) { continue; } buffer.setAccessible(true); if (!input) { BufferInfo info = mDequeuedOutputInfos.get(i); if (info != null) { buffer.limit(info.offset + info.size).position(info.offset); } } } indices.clear(); } if (input) { mCachedInputBuffers = buffers; } else { Loading Loading @@ -3954,6 +3997,9 @@ final public class MediaCodec { + "Please obtain MediaCodec.LinearBlock or HardwareBuffer " + "objects and attach to QueueRequest objects."); } if (mCachedInputBuffers == null) { cacheBuffersLocked(true /* input */); } if (mCachedInputBuffers == null) { throw new IllegalStateException(); } Loading Loading @@ -3992,6 +4038,9 @@ final public class MediaCodec { + "is not compatible with CONFIGURE_FLAG_USE_BLOCK_MODEL. " + "Please use getOutputFrame to get output frames."); } if (mCachedOutputBuffers == null) { cacheBuffersLocked(false /* input */); } if (mCachedOutputBuffers == null) { throw new IllegalStateException(); } Loading Loading @@ -4030,7 +4079,7 @@ final public class MediaCodec { } ByteBuffer newBuffer = getBuffer(true /* input */, index); synchronized (mBufferLock) { invalidateByteBuffer(mCachedInputBuffers, index); invalidateByteBufferLocked(mCachedInputBuffers, index, true /* input */); mDequeuedInputBuffers.put(index, newBuffer); } return newBuffer; Loading Loading @@ -4067,7 +4116,7 @@ final public class MediaCodec { } Image newImage = getImage(true /* input */, index); synchronized (mBufferLock) { invalidateByteBuffer(mCachedInputBuffers, index); invalidateByteBufferLocked(mCachedInputBuffers, index, true /* input */); mDequeuedInputBuffers.put(index, newImage); } return newImage; Loading Loading @@ -4103,7 +4152,7 @@ final public class MediaCodec { } ByteBuffer newBuffer = getBuffer(false /* input */, index); synchronized (mBufferLock) { invalidateByteBuffer(mCachedOutputBuffers, index); invalidateByteBufferLocked(mCachedOutputBuffers, index, false /* input */); mDequeuedOutputBuffers.put(index, newBuffer); } return newBuffer; Loading Loading @@ -4138,7 +4187,7 @@ final public class MediaCodec { } Image newImage = getImage(false /* input */, index); synchronized (mBufferLock) { invalidateByteBuffer(mCachedOutputBuffers, index); invalidateByteBufferLocked(mCachedOutputBuffers, index, false /* input */); mDequeuedOutputBuffers.put(index, newImage); } return newImage; Loading Loading
media/java/android/media/MediaCodec.java +90 −41 Original line number Diff line number Diff line Loading @@ -45,6 +45,7 @@ import java.nio.ByteOrder; import java.nio.ReadOnlyBufferException; import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; Loading Loading @@ -1803,7 +1804,7 @@ final public class MediaCodec { synchronized(mBufferLock) { switch (mBufferMode) { case BUFFER_MODE_LEGACY: validateInputByteBuffer(mCachedInputBuffers, index); validateInputByteBufferLocked(mCachedInputBuffers, index); break; case BUFFER_MODE_BLOCK: while (mQueueRequests.size() <= index) { Loading Loading @@ -1832,7 +1833,7 @@ final public class MediaCodec { synchronized(mBufferLock) { switch (mBufferMode) { case BUFFER_MODE_LEGACY: validateOutputByteBuffer(mCachedOutputBuffers, index, info); validateOutputByteBufferLocked(mCachedOutputBuffers, index, info); break; case BUFFER_MODE_BLOCK: while (mOutputFrames.size() <= index) { Loading Loading @@ -2320,10 +2321,6 @@ final public class MediaCodec { */ public final void start() { native_start(); synchronized(mBufferLock) { cacheBuffers(true /* input */); cacheBuffers(false /* input */); } } private native final void native_start(); Loading Loading @@ -2380,8 +2377,10 @@ final public class MediaCodec { */ public final void flush() { synchronized(mBufferLock) { invalidateByteBuffers(mCachedInputBuffers); invalidateByteBuffers(mCachedOutputBuffers); invalidateByteBuffersLocked(mCachedInputBuffers); invalidateByteBuffersLocked(mCachedOutputBuffers); mValidInputIndices.clear(); mValidOutputIndices.clear(); mDequeuedInputBuffers.clear(); mDequeuedOutputBuffers.clear(); } Loading Loading @@ -2665,14 +2664,14 @@ final public class MediaCodec { + "is not compatible with CONFIGURE_FLAG_USE_BLOCK_MODEL. " + "Please use getQueueRequest() to queue buffers"); } invalidateByteBuffer(mCachedInputBuffers, index); invalidateByteBufferLocked(mCachedInputBuffers, index, true /* input */); mDequeuedInputBuffers.remove(index); } try { native_queueInputBuffer( index, offset, size, presentationTimeUs, flags); } catch (CryptoException | IllegalStateException e) { revalidateByteBuffer(mCachedInputBuffers, index); revalidateByteBuffer(mCachedInputBuffers, index, true /* input */); throw e; } } Loading Loading @@ -2935,14 +2934,14 @@ final public class MediaCodec { + "is not compatible with CONFIGURE_FLAG_USE_BLOCK_MODEL. " + "Please use getQueueRequest() to queue buffers"); } invalidateByteBuffer(mCachedInputBuffers, index); invalidateByteBufferLocked(mCachedInputBuffers, index, true /* input */); mDequeuedInputBuffers.remove(index); } try { native_queueSecureInputBuffer( index, offset, info, presentationTimeUs, flags); } catch (CryptoException | IllegalStateException e) { revalidateByteBuffer(mCachedInputBuffers, index); revalidateByteBuffer(mCachedInputBuffers, index, true /* input */); throw e; } } Loading Loading @@ -2976,7 +2975,7 @@ final public class MediaCodec { int res = native_dequeueInputBuffer(timeoutUs); if (res >= 0) { synchronized(mBufferLock) { validateInputByteBuffer(mCachedInputBuffers, res); validateInputByteBufferLocked(mCachedInputBuffers, res); } } return res; Loading Loading @@ -3573,10 +3572,10 @@ final public class MediaCodec { int res = native_dequeueOutputBuffer(info, timeoutUs); synchronized (mBufferLock) { if (res == INFO_OUTPUT_BUFFERS_CHANGED) { cacheBuffers(false /* input */); cacheBuffersLocked(false /* input */); } else if (res >= 0) { validateOutputByteBuffer(mCachedOutputBuffers, res, info); if (mHasSurface) { validateOutputByteBufferLocked(mCachedOutputBuffers, res, info); if (mHasSurface || mCachedOutputBuffers == null) { mDequeuedOutputInfos.put(res, info.dup()); } } Loading Loading @@ -3670,9 +3669,9 @@ final public class MediaCodec { synchronized(mBufferLock) { switch (mBufferMode) { case BUFFER_MODE_LEGACY: invalidateByteBuffer(mCachedOutputBuffers, index); invalidateByteBufferLocked(mCachedOutputBuffers, index, false /* input */); mDequeuedOutputBuffers.remove(index); if (mHasSurface) { if (mHasSurface || mCachedOutputBuffers == null) { info = mDequeuedOutputInfos.remove(index); } break; Loading Loading @@ -3824,15 +3823,24 @@ final public class MediaCodec { private ByteBuffer[] mCachedInputBuffers; private ByteBuffer[] mCachedOutputBuffers; private BitSet mValidInputIndices = new BitSet(); private BitSet mValidOutputIndices = new BitSet(); private final BufferMap mDequeuedInputBuffers = new BufferMap(); private final BufferMap mDequeuedOutputBuffers = new BufferMap(); private final Map<Integer, BufferInfo> mDequeuedOutputInfos = new HashMap<Integer, BufferInfo>(); final private Object mBufferLock; private final void invalidateByteBuffer( @Nullable ByteBuffer[] buffers, int index) { if (buffers != null && index >= 0 && index < buffers.length) { private void invalidateByteBufferLocked( @Nullable ByteBuffer[] buffers, int index, boolean input) { if (buffers == null) { if (index < 0) { throw new IllegalStateException("index is negative (" + index + ")"); } BitSet indices = input ? mValidInputIndices : mValidOutputIndices; indices.clear(index); } else if (index >= 0 && index < buffers.length) { ByteBuffer buffer = buffers[index]; if (buffer != null) { buffer.setAccessible(false); Loading @@ -3840,9 +3848,14 @@ final public class MediaCodec { } } private final void validateInputByteBuffer( private void validateInputByteBufferLocked( @Nullable ByteBuffer[] buffers, int index) { if (buffers != null && index >= 0 && index < buffers.length) { if (buffers == null) { if (index < 0) { throw new IllegalStateException("index is negative (" + index + ")"); } mValidInputIndices.set(index); } else if (index >= 0 && index < buffers.length) { ByteBuffer buffer = buffers[index]; if (buffer != null) { buffer.setAccessible(true); Loading @@ -3851,10 +3864,16 @@ final public class MediaCodec { } } private final void revalidateByteBuffer( @Nullable ByteBuffer[] buffers, int index) { private void revalidateByteBuffer( @Nullable ByteBuffer[] buffers, int index, boolean input) { synchronized(mBufferLock) { if (buffers != null && index >= 0 && index < buffers.length) { if (buffers == null) { if (index < 0) { throw new IllegalStateException("index is negative (" + index + ")"); } BitSet indices = input ? mValidInputIndices : mValidOutputIndices; indices.set(index); } else if (index >= 0 && index < buffers.length) { ByteBuffer buffer = buffers[index]; if (buffer != null) { buffer.setAccessible(true); Loading @@ -3863,9 +3882,14 @@ final public class MediaCodec { } } private final void validateOutputByteBuffer( private void validateOutputByteBufferLocked( @Nullable ByteBuffer[] buffers, int index, @NonNull BufferInfo info) { if (buffers != null && index >= 0 && index < buffers.length) { if (buffers == null) { if (index < 0) { throw new IllegalStateException("index is negative (" + index + ")"); } mValidOutputIndices.set(index); } else if (index >= 0 && index < buffers.length) { ByteBuffer buffer = buffers[index]; if (buffer != null) { buffer.setAccessible(true); Loading @@ -3874,7 +3898,7 @@ final public class MediaCodec { } } private final void invalidateByteBuffers(@Nullable ByteBuffer[] buffers) { private void invalidateByteBuffersLocked(@Nullable ByteBuffer[] buffers) { if (buffers != null) { for (ByteBuffer buffer: buffers) { if (buffer != null) { Loading @@ -3884,27 +3908,29 @@ final public class MediaCodec { } } private final void freeByteBuffer(@Nullable ByteBuffer buffer) { private void freeByteBufferLocked(@Nullable ByteBuffer buffer) { if (buffer != null /* && buffer.isDirect() */) { // all of our ByteBuffers are direct java.nio.NioUtils.freeDirectBuffer(buffer); } } private final void freeByteBuffers(@Nullable ByteBuffer[] buffers) { private void freeByteBuffersLocked(@Nullable ByteBuffer[] buffers) { if (buffers != null) { for (ByteBuffer buffer: buffers) { freeByteBuffer(buffer); freeByteBufferLocked(buffer); } } } private final void freeAllTrackedBuffers() { private void freeAllTrackedBuffers() { synchronized(mBufferLock) { freeByteBuffers(mCachedInputBuffers); freeByteBuffers(mCachedOutputBuffers); freeByteBuffersLocked(mCachedInputBuffers); freeByteBuffersLocked(mCachedOutputBuffers); mCachedInputBuffers = null; mCachedOutputBuffers = null; mValidInputIndices.clear(); mValidOutputIndices.clear(); mDequeuedInputBuffers.clear(); mDequeuedOutputBuffers.clear(); mQueueRequests.clear(); Loading @@ -3912,14 +3938,31 @@ final public class MediaCodec { } } private final void cacheBuffers(boolean input) { private void cacheBuffersLocked(boolean input) { ByteBuffer[] buffers = null; try { buffers = getBuffers(input); invalidateByteBuffers(buffers); invalidateByteBuffersLocked(buffers); } catch (IllegalStateException e) { // we don't get buffers in async mode } if (buffers != null) { BitSet indices = input ? mValidInputIndices : mValidOutputIndices; for (int i = 0; i < buffers.length; ++i) { ByteBuffer buffer = buffers[i]; if (buffer == null || !indices.get(i)) { continue; } buffer.setAccessible(true); if (!input) { BufferInfo info = mDequeuedOutputInfos.get(i); if (info != null) { buffer.limit(info.offset + info.size).position(info.offset); } } } indices.clear(); } if (input) { mCachedInputBuffers = buffers; } else { Loading Loading @@ -3954,6 +3997,9 @@ final public class MediaCodec { + "Please obtain MediaCodec.LinearBlock or HardwareBuffer " + "objects and attach to QueueRequest objects."); } if (mCachedInputBuffers == null) { cacheBuffersLocked(true /* input */); } if (mCachedInputBuffers == null) { throw new IllegalStateException(); } Loading Loading @@ -3992,6 +4038,9 @@ final public class MediaCodec { + "is not compatible with CONFIGURE_FLAG_USE_BLOCK_MODEL. " + "Please use getOutputFrame to get output frames."); } if (mCachedOutputBuffers == null) { cacheBuffersLocked(false /* input */); } if (mCachedOutputBuffers == null) { throw new IllegalStateException(); } Loading Loading @@ -4030,7 +4079,7 @@ final public class MediaCodec { } ByteBuffer newBuffer = getBuffer(true /* input */, index); synchronized (mBufferLock) { invalidateByteBuffer(mCachedInputBuffers, index); invalidateByteBufferLocked(mCachedInputBuffers, index, true /* input */); mDequeuedInputBuffers.put(index, newBuffer); } return newBuffer; Loading Loading @@ -4067,7 +4116,7 @@ final public class MediaCodec { } Image newImage = getImage(true /* input */, index); synchronized (mBufferLock) { invalidateByteBuffer(mCachedInputBuffers, index); invalidateByteBufferLocked(mCachedInputBuffers, index, true /* input */); mDequeuedInputBuffers.put(index, newImage); } return newImage; Loading Loading @@ -4103,7 +4152,7 @@ final public class MediaCodec { } ByteBuffer newBuffer = getBuffer(false /* input */, index); synchronized (mBufferLock) { invalidateByteBuffer(mCachedOutputBuffers, index); invalidateByteBufferLocked(mCachedOutputBuffers, index, false /* input */); mDequeuedOutputBuffers.put(index, newBuffer); } return newBuffer; Loading Loading @@ -4138,7 +4187,7 @@ final public class MediaCodec { } Image newImage = getImage(false /* input */, index); synchronized (mBufferLock) { invalidateByteBuffer(mCachedOutputBuffers, index); invalidateByteBufferLocked(mCachedOutputBuffers, index, false /* input */); mDequeuedOutputBuffers.put(index, newImage); } return newImage; Loading